diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 854c56a..3dd9855 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: - name: Govulncheck run: govulncheck ./... continue-on-error: true + - run: make commentcheck - run: go test -race -shuffle=on -cover -coverprofile=coverage.out ./... - run: go build ./... - run: make cover diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5d95071..d63c6cc 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -34,15 +34,15 @@ jobs: - name: Lint run: make lint + - name: Comment check + run: make commentcheck + - name: Test (race) run: make test-race - name: Cover run: make cover - - name: Comment check - run: make commentcheck - - name: Vulnerability check (non-blocking) continue-on-error: true run: | diff --git a/Makefile b/Makefile index 11d2cfc..21431e9 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ GO ?= go FMT_PKGS := $(shell $(GO) list $(PKG)) FMT_DIRS := $(shell $(GO) list -f '{{.Dir}}' $(PKG)) -.PHONY: all init tidy fmt lint vet vuln test test-race cover cover-html build ci clean +.PHONY: all init tidy fmt lint vet vuln commentcheck test test-race cover cover-html build ci clean all: build @@ -25,11 +25,14 @@ fmt: $(GO) run mvdan.cc/gofumpt@latest -w $(FMT_DIRS) lint: - $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - golangci-lint run --timeout=5m + $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + golangci-lint run --timeout=5m + +commentcheck: + $(GO) run ./cmd/commentcheck vet: - $(GO) vet $(PKG) + $(GO) vet $(PKG) test: mkdir -p $(BUILD_DIR) @@ -50,9 +53,9 @@ build: $(GO) build -trimpath -buildvcs=false -ldflags="-s -w" -o $(BUILD_DIR)/$(APP) ./cmd/hclalign vuln: - $(GO) run golang.org/x/vuln/cmd/govulncheck@latest $(PKG) + $(GO) run golang.org/x/vuln/cmd/govulncheck@latest $(PKG) -ci: tidy fmt lint vuln test cover build +ci: tidy fmt lint vuln commentcheck test cover build clean: rm -rf $(BUILD_DIR) diff --git a/cmd/commentcheck/main.go b/cmd/commentcheck/main.go new file mode 100644 index 0000000..9f84c22 --- /dev/null +++ b/cmd/commentcheck/main.go @@ -0,0 +1,62 @@ +// cmd/commentcheck/main.go +package main + +import ( + "fmt" + "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + "strings" +) + +func check(root string) error { + var bad []string + walk := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + if d.Name() == "vendor" || strings.HasPrefix(d.Name(), ".") { + return filepath.SkipDir + } + return nil + } + if filepath.Ext(path) != ".go" { + return nil + } + fset := token.NewFileSet() + src, err := os.ReadFile(path) + if err != nil { + return err + } + file, err := parser.ParseFile(fset, path, src, parser.ParseComments) + if err != nil { + return err + } + for _, cg := range file.Comments { + for _, c := range cg.List { + if fset.Position(c.Slash).Line > 1 { + bad = append(bad, path) + return nil + } + } + } + return nil + } + if err := filepath.WalkDir(root, walk); err != nil { + return err + } + if len(bad) > 0 { + return fmt.Errorf("comments beyond first line: %s", strings.Join(bad, ", ")) + } + return nil +} + +func main() { + if err := check("."); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/commentcheck/main_test.go b/cmd/commentcheck/main_test.go new file mode 100644 index 0000000..ef62e55 --- /dev/null +++ b/cmd/commentcheck/main_test.go @@ -0,0 +1,33 @@ +// cmd/commentcheck/main_test.go +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestCheck(t *testing.T) { + t.Run("compliant", func(t *testing.T) { + dir := t.TempDir() + write(t, dir, "ok.go", "// ok.go\npackage main\n") + if err := check(dir); err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + t.Run("noncompliant", func(t *testing.T) { + dir := t.TempDir() + write(t, dir, "bad.go", "// bad.go\npackage main\n// bad\n") + if err := check(dir); err == nil { + t.Fatalf("expected error") + } + }) +} + +func write(t *testing.T, dir, name, content string) { + t.Helper() + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatalf("write %s: %v", name, err) + } +}