diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cd67b9653..0a45569ec 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: 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/.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 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 61302f6f7..f86326cfa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,47 +1,37 @@ -# https://golangci-lint.run/usage/configuration version: "2" - run: - timeout: 5m - tests: true concurrency: 4 - + tests: true linters: enable: - - govet - - errcheck - - staticcheck - - revive - - ineffassign - - unused - - misspell - - nakedret - bodyclose - gocritic - - makezero - gosec + - makezero + - misspell + - nakedret + - revive + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ 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 - + - "all" + - -QF1008 + - -ST1000 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/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", }, diff --git a/script/lint b/script/lint new file mode 100755 index 000000000..58884e3a0 --- /dev/null +++ b/script/lint @@ -0,0 +1,15 @@ +set -eu + +# first run go fmt +gofmt -s -w . + +BINDIR="$(git rev-parse --show-toplevel)"/bin +BINARY=$BINDIR/golangci-lint +GOLANGCI_LINT_VERSION=v2.2.1 + + +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