From be401faef2dfcde1739c016cd6e2346f40be3f80 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 25 Jun 2025 12:14:47 +0200 Subject: [PATCH 01/12] Add issues type filter --- pkg/github/issues.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index b4c64c8de..c7a86abc1 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -192,6 +192,9 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) ( if err != nil { return mcp.NewToolResultError(err.Error()), nil } + if !strings.Contains(query, "is:issue") { + query = query + " is:issue" + } sort, err := OptionalParam[string](request, "sort") if err != nil { return mcp.NewToolResultError(err.Error()), nil From ae5a4d7e8fb42823bb17b377d00ec3129fe201ed Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 25 Jun 2025 14:42:41 +0200 Subject: [PATCH 02/12] Add e2e test --- e2e/e2e_test.go | 173 +++++++++++++++++++++++++++++++++++++++++++ pkg/github/issues.go | 2 +- 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index bc5a3fde3..61739aaf5 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1447,6 +1447,179 @@ func TestPullRequestReviewCommentSubmit(t *testing.T) { require.Equal(t, 3, len(comments), "expected to find three review comments") } +func TestListIssues(t *testing.T) { + t.Parallel() + + mcpClient := setupMCPClient(t) + + ctx := context.Background() + + // First, who am I + getMeRequest := mcp.CallToolRequest{} + getMeRequest.Params.Name = "get_me" + + t.Log("Getting current user...") + resp, err := mcpClient.CallTool(ctx, getMeRequest) + require.NoError(t, err, "expected to call 'get_me' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + require.False(t, resp.IsError, "expected result not to be an error") + require.Len(t, resp.Content, 1, "expected content to have one item") + + textContent, ok := resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + + var trimmedGetMeText struct { + Login string `json:"login"` + } + err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText) + require.NoError(t, err, "expected to unmarshal text content successfully") + + currentOwner := trimmedGetMeText.Login + + // Then create a repository with a README (via autoInit) + repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli()) + createRepoRequest := mcp.CallToolRequest{} + createRepoRequest.Params.Name = "create_repository" + createRepoRequest.Params.Arguments = map[string]any{ + "name": repoName, + "private": true, + "autoInit": true, + } + + t.Logf("Creating repository %s/%s...", currentOwner, repoName) + _, err = mcpClient.CallTool(ctx, createRepoRequest) + require.NoError(t, err, "expected to call 'create_repository' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // Cleanup the repository after the test + t.Cleanup(func() { + // MCP Server doesn't support deletions, but we can use the GitHub Client + ghClient := getRESTClient(t) + t.Logf("Deleting repository %s/%s...", currentOwner, repoName) + ghClient.Repositories.Delete(context.Background(), currentOwner, repoName) + // require.NoError(t, err, "expected to delete repository successfully") + }) + + // Create a branch on which to create a new commit + createBranchRequest := mcp.CallToolRequest{} + createBranchRequest.Params.Name = "create_branch" + createBranchRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "branch": "test-branch", + "from_branch": "main", + } + + t.Logf("Creating branch in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, createBranchRequest) + require.NoError(t, err, "expected to call 'create_branch' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // Create a commit with a new file + commitRequest := mcp.CallToolRequest{} + commitRequest.Params.Name = "create_or_update_file" + commitRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "path": "test-file.txt", + "content": fmt.Sprintf("Created by e2e test %s\nwith multiple lines", t.Name()), + "message": "Add test file", + "branch": "test-branch", + } + + t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, commitRequest) + require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + textContent, ok = resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + + var trimmedCommitText struct { + Commit struct { + SHA string `json:"sha"` + } `json:"commit"` + } + err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText) + require.NoError(t, err, "expected to unmarshal text content successfully") + + // Create a pull request + prRequest := mcp.CallToolRequest{} + prRequest.Params.Name = "create_pull_request" + prRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "title": "Test PR", + "body": "This is a test PR", + "head": "test-branch", + "base": "main", + } + + t.Logf("Creating pull request in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, prRequest) + require.NoError(t, err, "expected to call 'create_pull_request' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // Create a review for the pull request, but we can't approve it + // because the current owner also owns the PR. + createPendingPullRequestReviewRequest := mcp.CallToolRequest{} + createPendingPullRequestReviewRequest.Params.Name = "create_pending_pull_request_review" + createPendingPullRequestReviewRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "pullNumber": 1, + } + + t.Logf("Creating pending review for pull request in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, createPendingPullRequestReviewRequest) + require.NoError(t, err, "expected to call 'create_pending_pull_request_review' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + textContent, ok = resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + require.Equal(t, "pending pull request created", textContent.Text) + + t.Logf("Creating an issue in %s/%s...", currentOwner, repoName) + createIssueRequest := mcp.CallToolRequest{} + createIssueRequest.Params.Name = "create_issue" + createIssueRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "title": "Test Issue", + "body": "This is a test issue", + } + resp, err = mcpClient.CallTool(ctx, createIssueRequest) + require.NoError(t, err, "expected to call 'create_issue' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // List issues in the repository + listIssuesRequest := mcp.CallToolRequest{} + listIssuesRequest.Params.Name = "list_issues" + listIssuesRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + } + + t.Logf("Listing issues in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, listIssuesRequest) + require.NoError(t, err, "expected to call 'list_issues' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + textContent, ok = resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + + var issues []struct { + ID int `json:"id"` + Number int `json:"number"` + Title string `json:"title"` + Body string `json:"body"` + } + err = json.Unmarshal([]byte(textContent.Text), &issues) + require.NoError(t, err, "expected to unmarshal text content successfully") + require.Len(t, issues, 1, "expected to find one issue") +} + func TestPullRequestReviewDeletion(t *testing.T) { t.Parallel() diff --git a/pkg/github/issues.go b/pkg/github/issues.go index c7a86abc1..471b2077c 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -193,7 +193,7 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) ( return mcp.NewToolResultError(err.Error()), nil } if !strings.Contains(query, "is:issue") { - query = query + " is:issue" + query += " is:issue" } sort, err := OptionalParam[string](request, "sort") if err != nil { From 59919f00dfa19d5cbb2ed672985daffd57021e22 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 25 Jun 2025 14:51:21 +0200 Subject: [PATCH 03/12] Update e2e/e2e_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- e2e/e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 61739aaf5..3b96aa666 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1488,7 +1488,7 @@ func TestListIssues(t *testing.T) { } t.Logf("Creating repository %s/%s...", currentOwner, repoName) - _, err = mcpClient.CallTool(ctx, createRepoRequest) + resp, err := mcpClient.CallTool(ctx, createRepoRequest) require.NoError(t, err, "expected to call 'create_repository' tool successfully") require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) From 14a6ee330eca395b32f3b8d131990ec24de80ae3 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Fri, 27 Jun 2025 17:31:26 +0200 Subject: [PATCH 04/12] Add lint script and update golangci config --- .golangci.yml | 6 +- e2e/e2e_test.go | 173 ------------------------- script/lint | 327 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+), 176 deletions(-) create mode 100755 script/lint diff --git a/.golangci.yml b/.golangci.yml index 61302f6f7..0ceb6ef9b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,4 @@ # https://golangci-lint.run/usage/configuration -version: "2" run: timeout: 5m @@ -20,12 +19,13 @@ linters: - gocritic - makezero - gosec + settings: staticcheck: checks: - all - - '-QF1008' # Allow embedded structs to be referenced by field - - '-ST1000' # Do not require package comments + - "-QF1008" # Allow embedded structs to be referenced by field + - "-ST1000" # Do not require package comments revive: rules: - name: exported diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 3b96aa666..bc5a3fde3 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1447,179 +1447,6 @@ func TestPullRequestReviewCommentSubmit(t *testing.T) { require.Equal(t, 3, len(comments), "expected to find three review comments") } -func TestListIssues(t *testing.T) { - t.Parallel() - - mcpClient := setupMCPClient(t) - - ctx := context.Background() - - // First, who am I - getMeRequest := mcp.CallToolRequest{} - getMeRequest.Params.Name = "get_me" - - t.Log("Getting current user...") - resp, err := mcpClient.CallTool(ctx, getMeRequest) - require.NoError(t, err, "expected to call 'get_me' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - require.False(t, resp.IsError, "expected result not to be an error") - require.Len(t, resp.Content, 1, "expected content to have one item") - - textContent, ok := resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - - var trimmedGetMeText struct { - Login string `json:"login"` - } - err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText) - require.NoError(t, err, "expected to unmarshal text content successfully") - - currentOwner := trimmedGetMeText.Login - - // Then create a repository with a README (via autoInit) - repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli()) - createRepoRequest := mcp.CallToolRequest{} - createRepoRequest.Params.Name = "create_repository" - createRepoRequest.Params.Arguments = map[string]any{ - "name": repoName, - "private": true, - "autoInit": true, - } - - t.Logf("Creating repository %s/%s...", currentOwner, repoName) - resp, err := mcpClient.CallTool(ctx, createRepoRequest) - require.NoError(t, err, "expected to call 'create_repository' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // Cleanup the repository after the test - t.Cleanup(func() { - // MCP Server doesn't support deletions, but we can use the GitHub Client - ghClient := getRESTClient(t) - t.Logf("Deleting repository %s/%s...", currentOwner, repoName) - ghClient.Repositories.Delete(context.Background(), currentOwner, repoName) - // require.NoError(t, err, "expected to delete repository successfully") - }) - - // Create a branch on which to create a new commit - createBranchRequest := mcp.CallToolRequest{} - createBranchRequest.Params.Name = "create_branch" - createBranchRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "branch": "test-branch", - "from_branch": "main", - } - - t.Logf("Creating branch in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, createBranchRequest) - require.NoError(t, err, "expected to call 'create_branch' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // Create a commit with a new file - commitRequest := mcp.CallToolRequest{} - commitRequest.Params.Name = "create_or_update_file" - commitRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "path": "test-file.txt", - "content": fmt.Sprintf("Created by e2e test %s\nwith multiple lines", t.Name()), - "message": "Add test file", - "branch": "test-branch", - } - - t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, commitRequest) - require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - textContent, ok = resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - - var trimmedCommitText struct { - Commit struct { - SHA string `json:"sha"` - } `json:"commit"` - } - err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText) - require.NoError(t, err, "expected to unmarshal text content successfully") - - // Create a pull request - prRequest := mcp.CallToolRequest{} - prRequest.Params.Name = "create_pull_request" - prRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "title": "Test PR", - "body": "This is a test PR", - "head": "test-branch", - "base": "main", - } - - t.Logf("Creating pull request in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, prRequest) - require.NoError(t, err, "expected to call 'create_pull_request' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // Create a review for the pull request, but we can't approve it - // because the current owner also owns the PR. - createPendingPullRequestReviewRequest := mcp.CallToolRequest{} - createPendingPullRequestReviewRequest.Params.Name = "create_pending_pull_request_review" - createPendingPullRequestReviewRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "pullNumber": 1, - } - - t.Logf("Creating pending review for pull request in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, createPendingPullRequestReviewRequest) - require.NoError(t, err, "expected to call 'create_pending_pull_request_review' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - textContent, ok = resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - require.Equal(t, "pending pull request created", textContent.Text) - - t.Logf("Creating an issue in %s/%s...", currentOwner, repoName) - createIssueRequest := mcp.CallToolRequest{} - createIssueRequest.Params.Name = "create_issue" - createIssueRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "title": "Test Issue", - "body": "This is a test issue", - } - resp, err = mcpClient.CallTool(ctx, createIssueRequest) - require.NoError(t, err, "expected to call 'create_issue' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // List issues in the repository - listIssuesRequest := mcp.CallToolRequest{} - listIssuesRequest.Params.Name = "list_issues" - listIssuesRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - } - - t.Logf("Listing issues in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, listIssuesRequest) - require.NoError(t, err, "expected to call 'list_issues' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - textContent, ok = resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - - var issues []struct { - ID int `json:"id"` - Number int `json:"number"` - Title string `json:"title"` - Body string `json:"body"` - } - err = json.Unmarshal([]byte(textContent.Text), &issues) - require.NoError(t, err, "expected to unmarshal text content successfully") - require.Len(t, issues, 1, "expected to find one issue") -} - func TestPullRequestReviewDeletion(t *testing.T) { t.Parallel() diff --git a/script/lint b/script/lint new file mode 100755 index 000000000..365948c92 --- /dev/null +++ b/script/lint @@ -0,0 +1,327 @@ +#!/bin/bash + +set -euo pipefail + +# Colors for output (only when stdout is a terminal) +if [[ -t 1 ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + NC='\033[0m' # No Color +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + NC='' +fi + +# Script configuration +GOLANGCI_LINT_VERSION="v1.61.0" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +GOLANGCI_CONFIG="$PROJECT_ROOT/.golangci.yml" + +# Output file for JSON results +JSON_OUTPUT_FILE="${PROJECT_ROOT}/lint-results.json" + +show_usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +Run comprehensive linting checks on the Go codebase. + +This script performs the following checks: + 1. go mod verify - Verify module dependencies + 2. go mod tidy - Check if go.mod and go.sum are tidy + 3. gofmt - Check Go code formatting + 4. go vet - Run Go's built-in static analysis + 5. golangci-lint - Run comprehensive linting with custom config + +OPTIONS: + -h, --help Show this help message and exit + -v, --version Show golangci-lint version and exit + +OUTPUT: + Results are displayed in the terminal with colored output. + If issues are found, detailed results are saved to lint-results.json + in JSON format for integration with other tools. + +EXIT CODES: + 0 All checks passed + 1 One or more checks failed + +EXAMPLES: + $(basename "$0") # Run all lint checks + $(basename "$0") --help # Show this help + $(basename "$0") --version # Show golangci-lint version + +EOF +} + +show_version() { + echo "golangci-lint lint script" + echo "Target golangci-lint version: $GOLANGCI_LINT_VERSION" + if command -v golangci-lint >/dev/null 2>&1; then + echo "Installed golangci-lint version: $(golangci-lint version --format short 2>/dev/null || echo "unknown")" + else + echo "golangci-lint: not installed" + fi +} + +echo_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +echo_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +echo_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +echo_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if golangci-lint is installed and install if needed +install_golangci_lint() { + local current_version + + if command -v golangci-lint >/dev/null 2>&1; then + current_version=$(golangci-lint version --format short 2>/dev/null || echo "unknown") + if [[ "$current_version" == *"$GOLANGCI_LINT_VERSION"* ]]; then + echo_info "golangci-lint $GOLANGCI_LINT_VERSION is already installed" + return 0 + else + echo_warning "golangci-lint version mismatch. Current: $current_version, Required: $GOLANGCI_LINT_VERSION" + fi + fi + + echo_info "Installing golangci-lint $GOLANGCI_LINT_VERSION..." + + # Install using the official installation script + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" "$GOLANGCI_LINT_VERSION" + + # Verify installation + if ! command -v golangci-lint >/dev/null 2>&1; then + echo_error "Failed to install golangci-lint" + exit 1 + fi + + echo_success "golangci-lint installed successfully" +} + +# Run go mod tidy and check for changes +check_go_mod_tidy() { + echo_info "Checking go mod tidy..." + + # Create a temporary directory for comparison + local temp_dir + temp_dir=$(mktemp -d) + + # Copy current go.mod and go.sum + cp "$PROJECT_ROOT/go.mod" "$temp_dir/go.mod.orig" 2>/dev/null || true + cp "$PROJECT_ROOT/go.sum" "$temp_dir/go.sum.orig" 2>/dev/null || true + + # Run go mod tidy + cd "$PROJECT_ROOT" + if ! go mod tidy; then + echo_error "go mod tidy failed" + rm -rf "$temp_dir" + exit 1 + fi + + # Check for changes + local mod_changed=false + local sum_changed=false + + if ! diff -q "$PROJECT_ROOT/go.mod" "$temp_dir/go.mod.orig" >/dev/null 2>&1; then + mod_changed=true + fi + + if ! diff -q "$PROJECT_ROOT/go.sum" "$temp_dir/go.sum.orig" >/dev/null 2>&1; then + sum_changed=true + fi + + rm -rf "$temp_dir" + + if [[ "$mod_changed" == true ]] || [[ "$sum_changed" == true ]]; then + echo_error "go mod tidy resulted in changes. Please run 'go mod tidy' and commit the changes." + echo_error "Changed files:" + [[ "$mod_changed" == true ]] && echo_error " - go.mod" + [[ "$sum_changed" == true ]] && echo_error " - go.sum" + exit 1 + fi + + echo_success "go mod tidy check passed" +} + +# Run go mod verify +check_go_mod_verify() { + echo_info "Verifying go modules..." + + cd "$PROJECT_ROOT" + if ! go mod verify; then + echo_error "go mod verify failed" + exit 1 + fi + + echo_success "go mod verify passed" +} + +# Run go vet +check_go_vet() { + echo_info "Running go vet..." + + cd "$PROJECT_ROOT" + if ! go vet ./...; then + echo_error "go vet found issues" + exit 1 + fi + + echo_success "go vet passed" +} + +# Run gofmt and check for changes +check_go_fmt() { + echo_info "Checking go formatting..." + + cd "$PROJECT_ROOT" + local unformatted_files + unformatted_files=$(gofmt -l . | grep -v vendor/ | grep -v third-party/ || true) + + if [[ -n "$unformatted_files" ]]; then + echo_error "The following files are not properly formatted:" + echo "$unformatted_files" + echo_error "Please run 'gofmt -w .' or 'go fmt ./...' to fix formatting" + exit 1 + fi + + echo_success "go formatting check passed" +} + +# Run golangci-lint +run_golangci_lint() { + echo_info "Running golangci-lint..." + + cd "$PROJECT_ROOT" + + # Verify config file exists + if [[ ! -f "$GOLANGCI_CONFIG" ]]; then + echo_error "golangci-lint config file not found: $GOLANGCI_CONFIG" + exit 1 + fi + + # Run golangci-lint with JSON output + local exit_code=0 + golangci-lint run --config="$GOLANGCI_CONFIG" --out-format=json --print-issued-lines=false --print-linter-name=false > "$JSON_OUTPUT_FILE" || exit_code=$? + + # Filter out Go toolchain false positives if jq is available + if command -v jq >/dev/null 2>&1 && [[ -f "$JSON_OUTPUT_FILE" ]]; then + local filtered_output="${JSON_OUTPUT_FILE}.filtered" + jq --arg project_root "$PROJECT_ROOT" ' + { + Issues: [ + .Issues[] | select( + (.Pos.Filename | startswith($project_root)) and + (.Pos.Filename | contains("/go/pkg/mod/") | not) and + (.Pos.Filename | contains("/third-party/") | not) + ) + ], + Report: .Report + } + ' "$JSON_OUTPUT_FILE" > "$filtered_output" && mv "$filtered_output" "$JSON_OUTPUT_FILE" + fi + + # Check if there were any issues + local issue_count + issue_count=$(jq '.Issues | length' "$JSON_OUTPUT_FILE" 2>/dev/null || echo "0") + + if [[ "$exit_code" -ne 0 ]] && [[ "$issue_count" -gt 0 ]]; then + echo_error "golangci-lint found $issue_count issue(s) in project code" + echo_error "Detailed results saved to: $JSON_OUTPUT_FILE" + + # Also show a summary to stdout for immediate feedback + if command -v jq >/dev/null 2>&1 && [[ -f "$JSON_OUTPUT_FILE" ]]; then + echo_error "Summary of issues:" + jq -r '.Issues[] | " \(.Pos.Filename):\(.Pos.Line):\(.Pos.Column): \(.Text) (\(.FromLinter))"' "$JSON_OUTPUT_FILE" 2>/dev/null || true + fi + + exit 1 + fi + + echo_success "golangci-lint passed (checked $issue_count issues)" + # Clean up empty results file + if [[ "$issue_count" -eq 0 ]]; then + rm -f "$JSON_OUTPUT_FILE" + fi +} + +# Main execution +main() { + # Parse command line arguments + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -v|--version) + show_version + exit 0 + ;; + *) + echo_error "Unknown option: $1" + echo_error "Use --help for usage information" + exit 1 + ;; + esac + done + + echo_info "Starting lint checks for $(basename "$PROJECT_ROOT")..." + echo_info "Project root: $PROJECT_ROOT" + + # Ensure we're in a Go project + if [[ ! -f "$PROJECT_ROOT/go.mod" ]]; then + echo_error "Not a Go module (go.mod not found in $PROJECT_ROOT)" + exit 1 + fi + + # Install golangci-lint if needed + install_golangci_lint + + # Run all checks (fail fast) + check_go_mod_verify + check_go_mod_tidy + check_go_fmt + check_go_vet + run_golangci_lint + + echo_success "All lint checks passed! ✨" +} + +# Handle cleanup on script exit +cleanup() { + local exit_code=$? + if [[ $exit_code -ne 0 ]] && [[ -f "$JSON_OUTPUT_FILE" ]]; then + echo_info "Lint results available in: $JSON_OUTPUT_FILE" + fi +} + +trap cleanup EXIT + +# Check for required tools +if ! command -v curl >/dev/null 2>&1; then + echo_error "curl is required but not installed" + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo_warning "jq is not installed. JSON output parsing will be limited." +fi + +# Run main function +main "$@" From 9560caf81d2e724a8ce2ffa502e65729fa93d79f Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 13:47:45 +0200 Subject: [PATCH 05/12] Add lint script --- .github/workflows/go.yml | 3 + .github/workflows/lint.yaml | 45 ----- .golangci.yml | 13 ++ script/lint | 328 +----------------------------------- 4 files changed, 21 insertions(+), 368 deletions(-) delete mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cd67b9653..c99aaa8d7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,6 +25,9 @@ jobs: - name: Download dependencies run: go mod download + - name: Lint code + run: script/lint + - name: Run unit tests run: go test -race ./... diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 9fa416abd..000000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,45 +0,0 @@ -name: Lint -on: - push: - pull_request: - -permissions: - contents: read - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - - name: Verify dependencies - run: | - go mod verify - go mod download - - - name: Run checks - run: | - STATUS=0 - assert-nothing-changed() { - local diff - "$@" >/dev/null || return 1 - if ! diff="$(git diff -U1 --color --exit-code)"; then - printf '\e[31mError: running `\e[1m%s\e[22m` results in modifications that you must check into version control:\e[0m\n%s\n\n' "$*" "$diff" >&2 - git checkout -- . - STATUS=1 - fi - } - assert-nothing-changed go mod tidy - exit $STATUS - - - name: golangci-lint - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 - with: - version: v2.1.6 diff --git a/.golangci.yml b/.golangci.yml index 0ceb6ef9b..d303fa41f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,6 +4,8 @@ run: timeout: 5m tests: true concurrency: 4 + skip-dirs: + - "../../go/pkg/mod" linters: enable: @@ -34,6 +36,17 @@ linters: disabled: true - name: package-comments disabled: true + exclusions: + paths: + - vendor + - .git + - .vscode + - .idea + - .DS_Store + - go.sum + - go.mod + - LICENSE + - README.md formatters: enable: diff --git a/script/lint b/script/lint index 365948c92..86b5beb45 100755 --- a/script/lint +++ b/script/lint @@ -1,327 +1,9 @@ -#!/bin/bash +#!/usr/bin/env bash -set -euo pipefail +set -eEuo pipefail -# Colors for output (only when stdout is a terminal) -if [[ -t 1 ]]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[1;33m' - BLUE='\033[0;34m' - NC='\033[0m' # No Color -else - RED='' - GREEN='' - YELLOW='' - BLUE='' - NC='' -fi +# golangci-lint config file is .golangci.yaml +# --out-format=json --print-issued-lines=false --print-linter-name=false +golangci-lint run "$@" -# Script configuration -GOLANGCI_LINT_VERSION="v1.61.0" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -GOLANGCI_CONFIG="$PROJECT_ROOT/.golangci.yml" -# Output file for JSON results -JSON_OUTPUT_FILE="${PROJECT_ROOT}/lint-results.json" - -show_usage() { - cat << EOF -Usage: $(basename "$0") [OPTIONS] - -Run comprehensive linting checks on the Go codebase. - -This script performs the following checks: - 1. go mod verify - Verify module dependencies - 2. go mod tidy - Check if go.mod and go.sum are tidy - 3. gofmt - Check Go code formatting - 4. go vet - Run Go's built-in static analysis - 5. golangci-lint - Run comprehensive linting with custom config - -OPTIONS: - -h, --help Show this help message and exit - -v, --version Show golangci-lint version and exit - -OUTPUT: - Results are displayed in the terminal with colored output. - If issues are found, detailed results are saved to lint-results.json - in JSON format for integration with other tools. - -EXIT CODES: - 0 All checks passed - 1 One or more checks failed - -EXAMPLES: - $(basename "$0") # Run all lint checks - $(basename "$0") --help # Show this help - $(basename "$0") --version # Show golangci-lint version - -EOF -} - -show_version() { - echo "golangci-lint lint script" - echo "Target golangci-lint version: $GOLANGCI_LINT_VERSION" - if command -v golangci-lint >/dev/null 2>&1; then - echo "Installed golangci-lint version: $(golangci-lint version --format short 2>/dev/null || echo "unknown")" - else - echo "golangci-lint: not installed" - fi -} - -echo_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -echo_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -echo_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -echo_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Check if golangci-lint is installed and install if needed -install_golangci_lint() { - local current_version - - if command -v golangci-lint >/dev/null 2>&1; then - current_version=$(golangci-lint version --format short 2>/dev/null || echo "unknown") - if [[ "$current_version" == *"$GOLANGCI_LINT_VERSION"* ]]; then - echo_info "golangci-lint $GOLANGCI_LINT_VERSION is already installed" - return 0 - else - echo_warning "golangci-lint version mismatch. Current: $current_version, Required: $GOLANGCI_LINT_VERSION" - fi - fi - - echo_info "Installing golangci-lint $GOLANGCI_LINT_VERSION..." - - # Install using the official installation script - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" "$GOLANGCI_LINT_VERSION" - - # Verify installation - if ! command -v golangci-lint >/dev/null 2>&1; then - echo_error "Failed to install golangci-lint" - exit 1 - fi - - echo_success "golangci-lint installed successfully" -} - -# Run go mod tidy and check for changes -check_go_mod_tidy() { - echo_info "Checking go mod tidy..." - - # Create a temporary directory for comparison - local temp_dir - temp_dir=$(mktemp -d) - - # Copy current go.mod and go.sum - cp "$PROJECT_ROOT/go.mod" "$temp_dir/go.mod.orig" 2>/dev/null || true - cp "$PROJECT_ROOT/go.sum" "$temp_dir/go.sum.orig" 2>/dev/null || true - - # Run go mod tidy - cd "$PROJECT_ROOT" - if ! go mod tidy; then - echo_error "go mod tidy failed" - rm -rf "$temp_dir" - exit 1 - fi - - # Check for changes - local mod_changed=false - local sum_changed=false - - if ! diff -q "$PROJECT_ROOT/go.mod" "$temp_dir/go.mod.orig" >/dev/null 2>&1; then - mod_changed=true - fi - - if ! diff -q "$PROJECT_ROOT/go.sum" "$temp_dir/go.sum.orig" >/dev/null 2>&1; then - sum_changed=true - fi - - rm -rf "$temp_dir" - - if [[ "$mod_changed" == true ]] || [[ "$sum_changed" == true ]]; then - echo_error "go mod tidy resulted in changes. Please run 'go mod tidy' and commit the changes." - echo_error "Changed files:" - [[ "$mod_changed" == true ]] && echo_error " - go.mod" - [[ "$sum_changed" == true ]] && echo_error " - go.sum" - exit 1 - fi - - echo_success "go mod tidy check passed" -} - -# Run go mod verify -check_go_mod_verify() { - echo_info "Verifying go modules..." - - cd "$PROJECT_ROOT" - if ! go mod verify; then - echo_error "go mod verify failed" - exit 1 - fi - - echo_success "go mod verify passed" -} - -# Run go vet -check_go_vet() { - echo_info "Running go vet..." - - cd "$PROJECT_ROOT" - if ! go vet ./...; then - echo_error "go vet found issues" - exit 1 - fi - - echo_success "go vet passed" -} - -# Run gofmt and check for changes -check_go_fmt() { - echo_info "Checking go formatting..." - - cd "$PROJECT_ROOT" - local unformatted_files - unformatted_files=$(gofmt -l . | grep -v vendor/ | grep -v third-party/ || true) - - if [[ -n "$unformatted_files" ]]; then - echo_error "The following files are not properly formatted:" - echo "$unformatted_files" - echo_error "Please run 'gofmt -w .' or 'go fmt ./...' to fix formatting" - exit 1 - fi - - echo_success "go formatting check passed" -} - -# Run golangci-lint -run_golangci_lint() { - echo_info "Running golangci-lint..." - - cd "$PROJECT_ROOT" - - # Verify config file exists - if [[ ! -f "$GOLANGCI_CONFIG" ]]; then - echo_error "golangci-lint config file not found: $GOLANGCI_CONFIG" - exit 1 - fi - - # Run golangci-lint with JSON output - local exit_code=0 - golangci-lint run --config="$GOLANGCI_CONFIG" --out-format=json --print-issued-lines=false --print-linter-name=false > "$JSON_OUTPUT_FILE" || exit_code=$? - - # Filter out Go toolchain false positives if jq is available - if command -v jq >/dev/null 2>&1 && [[ -f "$JSON_OUTPUT_FILE" ]]; then - local filtered_output="${JSON_OUTPUT_FILE}.filtered" - jq --arg project_root "$PROJECT_ROOT" ' - { - Issues: [ - .Issues[] | select( - (.Pos.Filename | startswith($project_root)) and - (.Pos.Filename | contains("/go/pkg/mod/") | not) and - (.Pos.Filename | contains("/third-party/") | not) - ) - ], - Report: .Report - } - ' "$JSON_OUTPUT_FILE" > "$filtered_output" && mv "$filtered_output" "$JSON_OUTPUT_FILE" - fi - - # Check if there were any issues - local issue_count - issue_count=$(jq '.Issues | length' "$JSON_OUTPUT_FILE" 2>/dev/null || echo "0") - - if [[ "$exit_code" -ne 0 ]] && [[ "$issue_count" -gt 0 ]]; then - echo_error "golangci-lint found $issue_count issue(s) in project code" - echo_error "Detailed results saved to: $JSON_OUTPUT_FILE" - - # Also show a summary to stdout for immediate feedback - if command -v jq >/dev/null 2>&1 && [[ -f "$JSON_OUTPUT_FILE" ]]; then - echo_error "Summary of issues:" - jq -r '.Issues[] | " \(.Pos.Filename):\(.Pos.Line):\(.Pos.Column): \(.Text) (\(.FromLinter))"' "$JSON_OUTPUT_FILE" 2>/dev/null || true - fi - - exit 1 - fi - - echo_success "golangci-lint passed (checked $issue_count issues)" - # Clean up empty results file - if [[ "$issue_count" -eq 0 ]]; then - rm -f "$JSON_OUTPUT_FILE" - fi -} - -# Main execution -main() { - # Parse command line arguments - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - show_usage - exit 0 - ;; - -v|--version) - show_version - exit 0 - ;; - *) - echo_error "Unknown option: $1" - echo_error "Use --help for usage information" - exit 1 - ;; - esac - done - - echo_info "Starting lint checks for $(basename "$PROJECT_ROOT")..." - echo_info "Project root: $PROJECT_ROOT" - - # Ensure we're in a Go project - if [[ ! -f "$PROJECT_ROOT/go.mod" ]]; then - echo_error "Not a Go module (go.mod not found in $PROJECT_ROOT)" - exit 1 - fi - - # Install golangci-lint if needed - install_golangci_lint - - # Run all checks (fail fast) - check_go_mod_verify - check_go_mod_tidy - check_go_fmt - check_go_vet - run_golangci_lint - - echo_success "All lint checks passed! ✨" -} - -# Handle cleanup on script exit -cleanup() { - local exit_code=$? - if [[ $exit_code -ne 0 ]] && [[ -f "$JSON_OUTPUT_FILE" ]]; then - echo_info "Lint results available in: $JSON_OUTPUT_FILE" - fi -} - -trap cleanup EXIT - -# Check for required tools -if ! command -v curl >/dev/null 2>&1; then - echo_error "curl is required but not installed" - exit 1 -fi - -if ! command -v jq >/dev/null 2>&1; then - echo_warning "jq is not installed. JSON output parsing will be limited." -fi - -# Run main function -main "$@" From a4ffe29445d718bf07e1ca0728e5142975e5b9c0 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 13:52:01 +0200 Subject: [PATCH 06/12] Install if not installed --- script/lint | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/lint b/script/lint index 86b5beb45..235a2ea59 100755 --- a/script/lint +++ b/script/lint @@ -2,8 +2,12 @@ set -eEuo pipefail -# golangci-lint config file is .golangci.yaml -# --out-format=json --print-issued-lines=false --print-linter-name=false +# Install golangci-lint if not already installed +if ! command -v golangci-lint &> /dev/null; then + echo "golangci-lint not found, installing..." + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +fi + golangci-lint run "$@" From 0e33a46c5170fb0ce1ca3f42eb59d10a80a315dc Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 14:11:28 +0200 Subject: [PATCH 07/12] Pass lint config --- script/lint | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lint b/script/lint index 235a2ea59..22c26444a 100755 --- a/script/lint +++ b/script/lint @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -eEuo pipefail +PROJECT_DIR="$( cd "$( dirname "$0" )/.." && pwd )" # Install golangci-lint if not already installed if ! command -v golangci-lint &> /dev/null; then @@ -8,6 +9,5 @@ if ! command -v golangci-lint &> /dev/null; then go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest fi -golangci-lint run "$@" - +golangci-lint run "$@" -c "${PROJECT_DIR}/.golangci.yml" --timeout 10m From 9c82e904aa57c45f04fa5ae2424a94e4f80f2ca1 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 14:17:02 +0200 Subject: [PATCH 08/12] Use action and rename workflow --- .github/workflows/go.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c99aaa8d7..632ac37e4 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,4 +1,4 @@ -name: Unit Tests +name: Build and Test Go Project on: [push, pull_request] permissions: @@ -26,7 +26,9 @@ jobs: run: go mod download - name: Lint code - run: script/lint + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 + with: + version: v2.1.6 - name: Run unit tests run: go test -race ./... From e056c7b3310c8c94cdd0116ba73f6b2b0f70c475 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 14:19:57 +0200 Subject: [PATCH 09/12] Back to reparate config --- .github/workflows/go.yml | 5 ----- .github/workflows/lint.yml | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 632ac37e4..0a45569ec 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,11 +25,6 @@ jobs: - name: Download dependencies run: go mod download - - name: Lint code - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 - with: - version: v2.1.6 - - name: Run unit tests run: go test -race ./... diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..b40193e72 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: golangci-lint +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.1 From 525d4b23aa458322614d43319368b516fb2419ec Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 14:34:13 +0200 Subject: [PATCH 10/12] Migrate config to v2 --- .golangci.yml | 71 +++++++++++++++------------------------------------ script/lint | 20 ++++++++------- 2 files changed, 32 insertions(+), 59 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d303fa41f..db8764c71 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,60 +1,31 @@ -# https://golangci-lint.run/usage/configuration - +version: "2" run: - timeout: 5m - tests: true concurrency: 4 - skip-dirs: - - "../../go/pkg/mod" - + tests: true linters: enable: - - govet - - errcheck - - staticcheck - - revive - - ineffassign - - unused - - misspell - - nakedret - bodyclose - gocritic - - makezero - gosec - - settings: - staticcheck: - checks: - - all - - "-QF1008" # Allow embedded structs to be referenced by field - - "-ST1000" # Do not require package comments - revive: - rules: - - name: exported - disabled: true - - name: exported - disabled: true - - name: package-comments - disabled: true + - makezero + - misspell + - nakedret + - revive exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling paths: - - vendor - - .git - - .vscode - - .idea - - .DS_Store - - go.sum - - go.mod - - LICENSE - - README.md - + - third_party$ + - builtin$ + - examples$ formatters: - enable: - - gofmt - - goimports - -output: - formats: - text: - print-linter-name: true - print-issued-lines: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/script/lint b/script/lint index 22c26444a..58884e3a0 100755 --- a/script/lint +++ b/script/lint @@ -1,13 +1,15 @@ -#!/usr/bin/env bash +set -eu -set -eEuo pipefail -PROJECT_DIR="$( cd "$( dirname "$0" )/.." && pwd )" +# first run go fmt +gofmt -s -w . + +BINDIR="$(git rev-parse --show-toplevel)"/bin +BINARY=$BINDIR/golangci-lint +GOLANGCI_LINT_VERSION=v2.2.1 -# Install golangci-lint if not already installed -if ! command -v golangci-lint &> /dev/null; then - echo "golangci-lint not found, installing..." - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest -fi -golangci-lint run "$@" -c "${PROJECT_DIR}/.golangci.yml" --timeout 10m +if [ ! -f "$BINARY" ]; then + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s "$GOLANGCI_LINT_VERSION" +fi +$BINARY run \ No newline at end of file From 3a043203cddc901ca31a04943e9aac7845f27712 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 14:53:57 +0200 Subject: [PATCH 11/12] Update config --- .gitignore | 1 + .golangci.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 12649366d..df489c390 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ __debug_bin* # Go vendor +bin/ diff --git a/.golangci.yml b/.golangci.yml index db8764c71..f86326cfa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,6 +22,12 @@ linters: - third_party$ - builtin$ - examples$ + settings: + staticcheck: + checks: + - "all" + - -QF1008 + - -ST1000 formatters: exclusions: generated: lax From 2e970cea29e9e9d740fc0f8d2bb9a0aae6ab43f5 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 30 Jun 2025 14:57:13 +0200 Subject: [PATCH 12/12] Lint code --- cmd/github-mcp-server/generate_docs.go | 8 ++++---- cmd/github-mcp-server/main.go | 2 +- pkg/errors/error_test.go | 2 +- pkg/github/actions_test.go | 4 ++-- pkg/github/issues.go | 2 +- pkg/github/repositories.go | 6 ++---- pkg/github/repository_resource.go | 2 +- pkg/raw/raw.go | 8 ++++---- pkg/raw/raw_test.go | 16 ++++++++-------- 9 files changed, 24 insertions(+), 26 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index ff0342ec2..dfd66d288 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -23,7 +23,7 @@ var generateDocsCmd = &cobra.Command{ Use: "generate-docs", Short: "Generate documentation for tools and toolsets", Long: `Generate the automated sections of README.md and docs/remote-server.md with current tool and toolset information.`, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return generateAllDocs() }, } @@ -33,17 +33,17 @@ func init() { } // mockGetClient returns a mock GitHub client for documentation generation -func mockGetClient(ctx context.Context) (*gogithub.Client, error) { +func mockGetClient(_ context.Context) (*gogithub.Client, error) { return gogithub.NewClient(nil), nil } // mockGetGQLClient returns a mock GraphQL client for documentation generation -func mockGetGQLClient(ctx context.Context) (*githubv4.Client, error) { +func mockGetGQLClient(_ context.Context) (*githubv4.Client, error) { return githubv4.NewClient(nil), nil } // mockGetRawClient returns a mock raw client for documentation generation -func mockGetRawClient(ctx context.Context) (*raw.Client, error) { +func mockGetRawClient(_ context.Context) (*raw.Client, error) { return nil, nil } diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index b39a8b7df..cad002666 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -103,7 +103,7 @@ func main() { } } -func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { +func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { from := []string{"_"} to := "-" for _, sep := range from { diff --git a/pkg/errors/error_test.go b/pkg/errors/error_test.go index 409f20545..e7a5b6ea1 100644 --- a/pkg/errors/error_test.go +++ b/pkg/errors/error_test.go @@ -260,7 +260,7 @@ func TestGitHubErrorContext(t *testing.T) { t.Run("NewGitHubAPIErrorToCtx with nil context does not error", func(t *testing.T) { // Given a nil context - var ctx context.Context = nil + var ctx context.Context // Create a mock GitHub response resp := &github.Response{ diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index 388c0bbe2..9303932d2 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -538,7 +538,7 @@ func Test_DownloadWorkflowRunArtifact(t *testing.T) { Pattern: "/repos/owner/repo/actions/artifacts/123/zip", Method: "GET", }, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { // GitHub returns a 302 redirect to the download URL w.Header().Set("Location", "https://api.github.com/repos/owner/repo/actions/artifacts/123/download") w.WriteHeader(http.StatusFound) @@ -1055,7 +1055,7 @@ func Test_GetJobLogs_WithContentReturn(t *testing.T) { logContent := "2023-01-01T10:00:00.000Z Starting job...\n2023-01-01T10:00:01.000Z Running tests...\n2023-01-01T10:00:02.000Z Job completed successfully" // Create a test server to serve log content - testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(logContent)) })) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 3242c2be9..6121786d2 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -893,7 +893,7 @@ func AssignCodingAgentPrompt(t translations.TranslationHelperFunc) (tool mcp.Pro return mcp.NewPrompt("AssignCodingAgent", mcp.WithPromptDescription(t("PROMPT_ASSIGN_CODING_AGENT_DESCRIPTION", "Assign GitHub Coding Agent to multiple tasks in a GitHub repository.")), mcp.WithArgument("repo", mcp.ArgumentDescription("The repository to assign tasks in (owner/repo)."), mcp.RequiredArgument()), - ), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { + ), func(_ context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { repo := request.Params.Arguments["repo"] messages := []mcp.PromptMessage{ diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index fa5d7338a..5b116745e 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -491,7 +491,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t return mcp.NewToolResultError(err.Error()), nil } - rawOpts := &raw.RawContentOpts{} + rawOpts := &raw.ContentOpts{} if strings.HasPrefix(ref, "refs/pull/") { prNumber := strings.TrimSuffix(strings.TrimPrefix(ref, "refs/pull/"), "/head") @@ -532,9 +532,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t _ = resp.Body.Close() }() - if resp.StatusCode != http.StatusOK { - // If the raw content is not found, we will fall back to the GitHub API (in case it is a directory) - } else { + if resp.StatusCode == http.StatusOK { // If the raw content is found, return it directly body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index fd2a04f89..a454db630 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -89,7 +89,7 @@ func RepositoryResourceContentsHandler(getClient GetClientFn, getRawClient raw.G } opts := &github.RepositoryContentGetOptions{} - rawOpts := &raw.RawContentOpts{} + rawOpts := &raw.ContentOpts{} sha, ok := request.Params.Arguments["sha"].([]string) if ok && len(sha) > 0 { diff --git a/pkg/raw/raw.go b/pkg/raw/raw.go index e6bab049d..17995ccae 100644 --- a/pkg/raw/raw.go +++ b/pkg/raw/raw.go @@ -41,9 +41,9 @@ func (c *Client) refURL(owner, repo, ref, path string) string { return c.url.JoinPath(owner, repo, ref, path).String() } -func (c *Client) URLFromOpts(opts *RawContentOpts, owner, repo, path string) string { +func (c *Client) URLFromOpts(opts *ContentOpts, owner, repo, path string) string { if opts == nil { - opts = &RawContentOpts{} + opts = &ContentOpts{} } if opts.SHA != "" { return c.commitURL(owner, repo, opts.SHA, path) @@ -56,13 +56,13 @@ func (c *Client) commitURL(owner, repo, sha, path string) string { return c.url.JoinPath(owner, repo, sha, path).String() } -type RawContentOpts struct { +type ContentOpts struct { Ref string SHA string } // GetRawContent fetches the raw content of a file from a GitHub repository. -func (c *Client) GetRawContent(ctx context.Context, owner, repo, path string, opts *RawContentOpts) (*http.Response, error) { +func (c *Client) GetRawContent(ctx context.Context, owner, repo, path string, opts *ContentOpts) (*http.Response, error) { url := c.URLFromOpts(opts, owner, repo, path) req, err := c.newRequest(ctx, "GET", url, nil) if err != nil { diff --git a/pkg/raw/raw_test.go b/pkg/raw/raw_test.go index bb9b23a28..f02033159 100644 --- a/pkg/raw/raw_test.go +++ b/pkg/raw/raw_test.go @@ -17,7 +17,7 @@ func TestGetRawContent(t *testing.T) { tests := []struct { name string pattern mock.EndpointPattern - opts *RawContentOpts + opts *ContentOpts owner, repo, path string statusCode int contentType string @@ -36,7 +36,7 @@ func TestGetRawContent(t *testing.T) { { name: "branch fetch success", pattern: GetRawReposContentsByOwnerByRepoByBranchByPath, - opts: &RawContentOpts{Ref: "refs/heads/main"}, + opts: &ContentOpts{Ref: "refs/heads/main"}, owner: "octocat", repo: "hello", path: "README.md", statusCode: 200, contentType: "text/plain", @@ -45,7 +45,7 @@ func TestGetRawContent(t *testing.T) { { name: "tag fetch success", pattern: GetRawReposContentsByOwnerByRepoByTagByPath, - opts: &RawContentOpts{Ref: "refs/tags/v1.0.0"}, + opts: &ContentOpts{Ref: "refs/tags/v1.0.0"}, owner: "octocat", repo: "hello", path: "README.md", statusCode: 200, contentType: "text/plain", @@ -54,7 +54,7 @@ func TestGetRawContent(t *testing.T) { { name: "sha fetch success", pattern: GetRawReposContentsByOwnerByRepoBySHAByPath, - opts: &RawContentOpts{SHA: "abc123"}, + opts: &ContentOpts{SHA: "abc123"}, owner: "octocat", repo: "hello", path: "README.md", statusCode: 200, contentType: "text/plain", @@ -107,7 +107,7 @@ func TestUrlFromOpts(t *testing.T) { tests := []struct { name string - opts *RawContentOpts + opts *ContentOpts owner string repo string path string @@ -121,19 +121,19 @@ func TestUrlFromOpts(t *testing.T) { }, { name: "ref branch", - opts: &RawContentOpts{Ref: "refs/heads/main"}, + opts: &ContentOpts{Ref: "refs/heads/main"}, owner: "octocat", repo: "hello", path: "README.md", want: "https://raw.example.com/octocat/hello/refs/heads/main/README.md", }, { name: "ref tag", - opts: &RawContentOpts{Ref: "refs/tags/v1.0.0"}, + opts: &ContentOpts{Ref: "refs/tags/v1.0.0"}, owner: "octocat", repo: "hello", path: "README.md", want: "https://raw.example.com/octocat/hello/refs/tags/v1.0.0/README.md", }, { name: "sha", - opts: &RawContentOpts{SHA: "abc123"}, + opts: &ContentOpts{SHA: "abc123"}, owner: "octocat", repo: "hello", path: "README.md", want: "https://raw.example.com/octocat/hello/abc123/README.md", },