From 2d2d449cb6eaabd0cf08ad5b1513b405a77c3328 Mon Sep 17 00:00:00 2001 From: keyskey <39644776+keyskey@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:56:07 +0900 Subject: [PATCH 1/2] docs: bump version examples to v0.2.0 Made-with: Cursor --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 82738b2..2dc93fe 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ go install github.com/keyskey/gobce/cmd/gobce@latest For CI and team-wide reproducibility, pin a version tag: ```bash -go install github.com/keyskey/gobce/cmd/gobce@v0.1.0 +go install github.com/keyskey/gobce/cmd/gobce@v0.2.0 ``` ## Binary Distribution @@ -37,7 +37,7 @@ Recommended archive set: - linux-arm64 - linux-amd64 -Install from a prebuilt archive (example: `v0.1.0`, `darwin-arm64`): +Install from a prebuilt archive (example: `v0.2.0`, `darwin-arm64`): ```bash curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install.sh" | sh @@ -46,7 +46,7 @@ curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install Install a specific version: ```bash -VERSION=v0.1.0 curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install.sh" | sh +VERSION=v0.2.0 curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install.sh" | sh ``` Install to a custom directory: @@ -125,7 +125,7 @@ During the pre-1.0 phase, this project is versioned as `0.x` while coverage logi Please assume that interfaces and output formats may change between `0.x` minor versions. -Version tags use the `vX.Y.Z` format (for example, `v0.1.0`). +Version tags use the `vX.Y.Z` format (for example, `v0.2.0`). ## Release Flow @@ -145,13 +145,13 @@ make tag-next TYPE=major ``` ```bash -git tag v0.1.0 -git push origin v0.1.0 +git tag v0.2.0 +git push origin v0.2.0 ``` After push: - GitHub Actions workflow `release` runs automatically -- GitHub Release is created for `v0.1.0` +- GitHub Release is created for `v0.2.0` - Release notes are generated from commit history - OS/arch archives and `checksums.txt` are uploaded From 4cebcfdcfcaf6fdfb6ffe0a9351f9b4b9ab1b1d5 Mon Sep 17 00:00:00 2001 From: keyskey <39644776+keyskey@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:27:26 +0900 Subject: [PATCH 2/2] test: Add regression test for fallthrough behavior in if statements Introduce a new test case in analyzer_test.go to validate the handling of fallthrough scenarios in if statements without else clauses. The test ensures that the coverage analysis correctly identifies implicit false paths when the next statement starts on a new line. Additionally, update the fallthroughCovered function in branches.go to improve the logic for determining coverage based on the nearest block after the if body. --- analyzer_test.go | 40 +++++++++++++++++++++++++++++++++++ internal/analyzer/branches.go | 16 +++++++++----- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/analyzer_test.go b/analyzer_test.go index 6b002cd..ce2e247 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -210,6 +210,46 @@ func score(v int) int { } } +// Regression: coverprofile ends the if-body block at the closing "}", but the next +// statement often starts on the following line; fallthrough must not require an exact +// start position match with body.End(). +func TestAnalyzeIfWithoutElseFallthroughNextLine(t *testing.T) { + tmp := t.TempDir() + srcPath := filepath.Join(tmp, "sample.go") + src := `package sample + +func Get(v int) int { + if v > 10 { + return 1 + } + return 0 +} +` + if err := os.WriteFile(srcPath, []byte(src), 0o644); err != nil { + t.Fatalf("write source: %v", err) + } + + coverPath := filepath.Join(tmp, "coverage.out") + coverage := strings.Join([]string{ + "mode: set", + srcPath + ":3.23,4.13 1 1", + srcPath + ":4.13,6.3 1 0", + srcPath + ":7.2,7.10 1 1", + }, "\n") + if err := os.WriteFile(coverPath, []byte(coverage), 0o644); err != nil { + t.Fatalf("write coverage: %v", err) + } + + result, err := Analyze(AnalyzeInput{CoverProfilePath: coverPath}) + if err != nil { + t.Fatalf("analyze: %v", err) + } + + if hasUncoveredBranchKind(result, "if_implicit_false_path") { + t.Fatalf("expected if_implicit_false_path covered when next-line fallthrough block has count") + } +} + func TestAnalyzeIfWithoutElseNonTerminatingBody(t *testing.T) { tmp := t.TempDir() srcPath := filepath.Join(tmp, "sample.go") diff --git a/internal/analyzer/branches.go b/internal/analyzer/branches.go index 601fa9d..5c19fd1 100644 --- a/internal/analyzer/branches.go +++ b/internal/analyzer/branches.go @@ -148,17 +148,23 @@ func ifBodyTerminates(body *ast.BlockStmt) bool { } } +// fallthroughCovered reports whether execution can reach the first statement after body +// (implicit else) according to coverage. The next cover block rarely starts at body.End() +// (closing brace); it usually begins at the following statement, so we take the nearest +// block in this file whose start is at or after body.End(). func fallthroughCovered(blocks []coverageBlock, fset *token.FileSet, body *ast.BlockStmt) bool { endPos := fset.Position(body.End()) - for _, b := range blocks { - if b.Count <= 0 { + var nearest *coverageBlock + for i := range blocks { + b := &blocks[i] + if positionLess(b.StartLine, b.StartCol, endPos.Line, endPos.Column) { continue } - if b.StartLine == endPos.Line && b.StartCol == endPos.Column { - return true + if nearest == nil || positionLess(b.StartLine, b.StartCol, nearest.StartLine, nearest.StartCol) { + nearest = b } } - return false + return nearest != nil && nearest.Count > 0 } func blockStmtCovered(blocks []coverageBlock, fset *token.FileSet, body *ast.BlockStmt) bool {