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
7 changes: 1 addition & 6 deletions pkg/actionpins/actionpins.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,6 @@ func ExtractVersion(uses string) string {
return after
}

// isValidFullSHA checks if a string is a valid 40-character hexadecimal SHA.
func isValidFullSHA(s string) bool {
return gitutil.IsValidFullSHA(s)
}

// findVersionBySHA returns the version string for a given SHA in the embedded pins
// for the specified repo. Returns "" if no matching pin is found.
func findVersionBySHA(repo, sha string) string {
Expand Down Expand Up @@ -311,7 +306,7 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro
}
actionPinsLog.Printf("Resolving action pin: repo=%s, version=%s, strict_mode=%t", actionRepo, version, ctx.StrictMode)

isAlreadySHA := isValidFullSHA(version)
isAlreadySHA := gitutil.IsValidFullSHA(version)

if ctx.Resolver != nil && !isAlreadySHA {
actionPinsLog.Printf("Attempting dynamic resolution for %s@%s", actionRepo, version)
Expand Down
7 changes: 0 additions & 7 deletions pkg/cli/compile_batch_operations.go

This file was deleted.

9 changes: 0 additions & 9 deletions pkg/cli/gateway_logs.go

This file was deleted.

1 change: 0 additions & 1 deletion pkg/cli/list_command.go

This file was deleted.

3 changes: 3 additions & 0 deletions pkg/cli/mcp_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ func GetBinaryPath() (string, error) {
return resolvedPath, nil
}

// boolPtr returns a pointer to the given bool value, used for optional *bool fields.
func boolPtr(b bool) *bool { return &b }

// logAndValidateBinaryPath determines the binary path, logs it, and validates it exists.
// Returns the detected binary path and an error if the path cannot be determined or if the file doesn't exist.
// This is a helper used by both runMCPServer and validateMCPServerConfiguration.
Expand Down
12 changes: 0 additions & 12 deletions pkg/cli/run_command.go

This file was deleted.

6 changes: 3 additions & 3 deletions pkg/cli/update_container_pins.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func UpdateContainerPins(ctx context.Context, workflowDir string, verbose bool)
}

// Attempt to resolve the digest without pulling.
digest, resolveErr := resolveContainerDigest(ctx, image, verbose)
digest, resolveErr := fetchContainerDigest(ctx, image, verbose)
if resolveErr != nil {
containerPinsLog.Printf("Failed to resolve digest for %s: %v", image, resolveErr)
failedImages = append(failedImages, imageFailure{image: image, reason: resolveErr.Error()})
Expand Down Expand Up @@ -221,14 +221,14 @@ func collectImagesFromLockFiles(workflowDir string) ([]string, error) {
// while still bounding any hung Docker daemon or slow network connections.
const dockerCmdTimeout = 60 * time.Second

// resolveContainerDigest returns the SHA-256 content digest for the given image tag.
// fetchContainerDigest returns the SHA-256 content digest for the given image tag.
// It tries three strategies in order:
// 1. "docker buildx imagetools inspect" (no pull, preferred — works when docker daemon is running)
// 2. "crane digest" (no pull, no daemon — works in CI without Docker)
// 3. "docker pull" + "docker inspect" (fallback that pulls the full image)
//
// Returns an error when all three strategies fail.
func resolveContainerDigest(ctx context.Context, image string, verbose bool) (string, error) {
func fetchContainerDigest(ctx context.Context, image string, verbose bool) (string, error) {
containerPinsLog.Printf("Resolving digest for container image: %s", image)

type strategy struct {
Expand Down
4 changes: 0 additions & 4 deletions pkg/cli/util.go

This file was deleted.

2 changes: 0 additions & 2 deletions pkg/cli/verify_command.go

This file was deleted.

67 changes: 5 additions & 62 deletions pkg/linters/errormessage/errormessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ package errormessage
import (
"go/ast"
"go/token"
"go/types"
"path/filepath"
"strconv"
"strings"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"

"github.com/github/gh-aw/pkg/linters/internal/nolint"
)

var (
Expand Down Expand Up @@ -41,7 +42,7 @@ func run(pass *analysis.Pass) (any, error) {
}

insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
noLintLinesByFile := buildNoLintLineIndex(pass)
noLintLinesByFile := nolint.BuildLineIndex(pass, "errormessage")

nodeFilter := []ast.Node{(*ast.CallExpr)(nil)}
insp.Preorder(nodeFilter, func(n ast.Node) {
Expand All @@ -54,7 +55,7 @@ func run(pass *analysis.Pass) (any, error) {
if !shouldCheckFile(pos.Filename, changed) || strings.HasSuffix(pos.Filename, "_test.go") {
return
}
if hasNoLintDirective(pos, noLintLinesByFile) {
if nolint.HasDirective(pos, noLintLinesByFile) {
return
}

Expand Down Expand Up @@ -242,63 +243,5 @@ func returnsError(pass *analysis.Pass, call *ast.CallExpr) bool {
if t == nil {
return false
}
return implementsError(t)
}

func implementsError(t types.Type) bool {
obj := types.Universe.Lookup("error")
if obj == nil {
return false
}
errIface, ok := obj.Type().Underlying().(*types.Interface)
if !ok {
return false
}

if types.Implements(t, errIface) {
return true
}
if p, ok := t.(*types.Pointer); ok {
return types.Implements(p, errIface)
}
return types.Implements(types.NewPointer(t), errIface)
}

func hasNoLintDirective(position token.Position, noLintLinesByFile map[string]map[int]struct{}) bool {
if position.Filename == "" {
return false
}

noLintLines := noLintLinesByFile[position.Filename]
if noLintLines == nil {
return false
}

_, sameLine := noLintLines[position.Line]
_, previousLine := noLintLines[position.Line-1]
return sameLine || previousLine
}

func buildNoLintLineIndex(pass *analysis.Pass) map[string]map[int]struct{} {
noLintLinesByFile := make(map[string]map[int]struct{}, len(pass.Files))
for _, file := range pass.Files {
filename := pass.Fset.PositionFor(file.Pos(), false).Filename
if filename == "" {
continue
}
for _, group := range file.Comments {
for _, comment := range group.List {
text := strings.TrimPrefix(comment.Text, "//")
if !strings.HasPrefix(text, "nolint:errormessage") && !strings.HasPrefix(text, "nolint:all") {
continue
}
line := pass.Fset.PositionFor(comment.Slash, false).Line
if noLintLinesByFile[filename] == nil {
noLintLinesByFile[filename] = make(map[int]struct{})
}
noLintLinesByFile[filename][line] = struct{}{}
}
}
}
return noLintLinesByFile
return nolint.ImplementsError(t)
}
69 changes: 5 additions & 64 deletions pkg/linters/errstringmatch/errstringmatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"

"github.com/github/gh-aw/pkg/linters/internal/nolint"
)

// Analyzer is the err-string-match analysis pass.
Expand All @@ -25,7 +27,7 @@ var Analyzer = &analysis.Analyzer{

func run(pass *analysis.Pass) (any, error) {
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
noLintLinesByFile := buildNoLintLineIndex(pass)
noLintLinesByFile := nolint.BuildLineIndex(pass, "errstringmatch")

nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
Expand Down Expand Up @@ -58,7 +60,7 @@ func run(pass *analysis.Pass) (any, error) {
if !isStringLiteral(pass, outer.Args[1]) {
return
}
if hasNoLintDirective(position, noLintLinesByFile) {
if nolint.HasDirective(position, noLintLinesByFile) {
return
}

Expand Down Expand Up @@ -103,29 +105,7 @@ func isErrDotError(pass *analysis.Pass, expr ast.Expr) bool {
if t == nil {
return false
}
return implementsError(pass, t)
}

// implementsError reports whether t implements the built-in error interface.
func implementsError(pass *analysis.Pass, t types.Type) bool {
errIface := pass.Pkg.Scope().Lookup("error")
if errIface == nil {
// Look up the universe scope.
obj := types.Universe.Lookup("error")
if obj == nil {
return false
}
iface, ok := obj.Type().Underlying().(*types.Interface)
if !ok {
return false
}
return types.Implements(t, iface) || types.Implements(types.NewPointer(t), iface)
}
iface, ok := errIface.Type().Underlying().(*types.Interface)
if !ok {
return false
}
return types.Implements(t, iface) || types.Implements(types.NewPointer(t), iface)
return nolint.ImplementsError(t)
}

// isStringLiteral returns true when expr is a string literal or untyped string constant.
Expand All @@ -142,42 +122,3 @@ func isStringLiteral(pass *analysis.Pass, expr ast.Expr) bool {
basic, ok := t.Underlying().(*types.Basic)
return ok && basic.Kind() == types.String
}

func hasNoLintDirective(position token.Position, noLintLinesByFile map[string]map[int]struct{}) bool {
if position.Filename == "" {
return false
}

noLintLines := noLintLinesByFile[position.Filename]
if noLintLines == nil {
return false
}

_, sameLine := noLintLines[position.Line]
_, previousLine := noLintLines[position.Line-1]
return sameLine || previousLine
}

func buildNoLintLineIndex(pass *analysis.Pass) map[string]map[int]struct{} {
noLintLinesByFile := make(map[string]map[int]struct{}, len(pass.Files))
for _, file := range pass.Files {
filename := pass.Fset.PositionFor(file.Pos(), false).Filename
if filename == "" {
continue
}
for _, group := range file.Comments {
for _, comment := range group.List {
text := strings.TrimPrefix(comment.Text, "//")
if !strings.HasPrefix(text, "nolint:errstringmatch") && !strings.HasPrefix(text, "nolint:all") {
continue
}
line := pass.Fset.PositionFor(comment.Slash, false).Line
if noLintLinesByFile[filename] == nil {
noLintLinesByFile[filename] = make(map[int]struct{})
}
noLintLinesByFile[filename][line] = struct{}{}
}
}
}
return noLintLinesByFile
}
77 changes: 77 additions & 0 deletions pkg/linters/internal/nolint/nolint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Package nolint provides shared helpers for nolint-directive detection
// used by linters within pkg/linters.
package nolint

import (
"go/token"
"go/types"
"strings"

"golang.org/x/tools/go/analysis"
)

// HasDirective reports whether the given source position is covered by a
// suppression directive for linterName (or "nolint:all"). Both same-line and
// previous-line directives are recognised, matching golangci-lint behaviour.
func HasDirective(position token.Position, idx map[string]map[int]struct{}) bool {
if position.Filename == "" {
return false
}

noLintLines := idx[position.Filename]
if noLintLines == nil {
return false
}

_, sameLine := noLintLines[position.Line]
_, previousLine := noLintLines[position.Line-1]
return sameLine || previousLine
}

// BuildLineIndex scans all comments in the analysis pass and returns a map
// from filename → set of line numbers that carry a nolint directive for
// linterName (e.g. "errstringmatch") or "all".
func BuildLineIndex(pass *analysis.Pass, linterName string) map[string]map[int]struct{} {
prefix := "nolint:" + linterName
noLintLinesByFile := make(map[string]map[int]struct{}, len(pass.Files))
for _, file := range pass.Files {
filename := pass.Fset.PositionFor(file.Pos(), false).Filename
if filename == "" {
continue
}
for _, group := range file.Comments {
for _, comment := range group.List {
text := strings.TrimPrefix(comment.Text, "//")
if !strings.HasPrefix(text, prefix) && !strings.HasPrefix(text, "nolint:all") {
continue
}
line := pass.Fset.PositionFor(comment.Slash, false).Line
if noLintLinesByFile[filename] == nil {
noLintLinesByFile[filename] = make(map[int]struct{})
}
noLintLinesByFile[filename][line] = struct{}{}
}
}
}
return noLintLinesByFile
}

// ImplementsError reports whether t implements the built-in error interface.
func ImplementsError(t types.Type) bool {
obj := types.Universe.Lookup("error")
if obj == nil {
return false
}
errIface, ok := obj.Type().Underlying().(*types.Interface)
if !ok {
return false
}

if types.Implements(t, errIface) {
return true
}
if p, ok := t.(*types.Pointer); ok {
return types.Implements(p, errIface)
}
return types.Implements(types.NewPointer(t), errIface)
}
6 changes: 3 additions & 3 deletions pkg/workflow/awf_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ func buildAWFImageTagWithDigests(imageTag string, workflowData *WorkflowData) st

parts := []string{imageTag}
for _, spec := range specs {
digest := resolveContainerDigest(spec.image, workflowData)
digest := lookupContainerDigest(spec.image, workflowData)
if digest == "" {
continue
}
Expand All @@ -576,9 +576,9 @@ func buildAWFImageTagWithDigests(imageTag string, workflowData *WorkflowData) st
return strings.Join(parts, ",")
}

// resolveContainerDigest resolves a container image digest from cache first, then
// lookupContainerDigest resolves a container image digest from cache first, then
// falls back to embedded container pins.
func resolveContainerDigest(image string, workflowData *WorkflowData) string {
func lookupContainerDigest(image string, workflowData *WorkflowData) string {
var cache *ActionCache
if workflowData != nil {
cache = workflowData.ActionCache
Expand Down
Loading
Loading