From da330f4e9c80c8e034b513f2e5cd533ee2e394ee Mon Sep 17 00:00:00 2001 From: Robert Sworder Date: Tue, 17 Mar 2026 15:50:28 +0000 Subject: [PATCH 1/3] Add variable functions to coverage Some projects have functions in the form: ``` var foo = func() { // Some code here. } ``` This commit adds this code to code coverage metrics. --- README.md | 18 +++++++++--------- pkg/source/source.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 53fddad..2e6dd36 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,12 @@ The config file is a json file with the following structure: } } ``` -As output formats, you can choose between `cobertura` and `textfmt`. The first is described in the cobertura section and +As output formats, you can choose between `cobertura` and `textfmt`. The first is described in the cobertura section and the second is the default go coverage format. -Complexity only apply for the `cobertura` format. The complexity type can be either `cognitive` or `cyclomatic`. -The difference between these metrics is described in the cobertura section. +Complexity only applies for the `cobertura` format. The complexity type can be either `cognitive` or `cyclomatic`. +The difference between these metrics is described in the cobertura section. -The `SourcePath` is given relative to the working directory (starting with a `./`) or as absolute path. +The `SourcePath` is given relative to the working directory (starting with a `./`) or as absolute path. The `ExcludePaths` are paths that should be excluded from the coverage report. If a directory is in the exclude list, all files in this directory or subdirectory are excluded. They are given relative to the `SourcePath`. There are no white card or regex matching here. Thus, If you want to execlude more than one directory, you have to add them all to the list. The example above would exclude all files in the `vendor` directory in the working directory. @@ -57,7 +57,7 @@ The `Cleaner` part contains the filters that should be applied to the source cod ## The accuracy of `go test -coverprofile` The `go test -coverprofile` command is a great tool to get coverage information about your project. -However, it measures the coverage on a bock level. This means that if you function contains empty lines, only comments, +However, it measures the coverage on a block level. This means that if you function contains empty lines, only comments, or lines with only a closing bracket, they will be counted in line metrics. This project tries to solve this problem by using the `go/ast` package to determine the actual lines of code from the source. @@ -71,7 +71,7 @@ Thus, we add branch coverage on method and file level. Where such multi conditio There are parts of the source code that may not be included in the coverage report. At the moment, the following parts can be excluded: * Generated files - * Files that fellows [this convention](https://go.dev/s/generatedcode) are excluded + * Files that follow [this convention](https://go.dev/s/generatedcode) are excluded * None code lines * Empty lines * Lines that only contain a comment @@ -85,7 +85,7 @@ You can activate these filters by using the corresponding config values. # Cobertura Format The cobertura format is a widely used format for coverage reports. It is supported by many tools like Jenkins. -It is an XML format that contains the coverage information for each file and package. +It is an XML format that contains the coverage information for each file and package. Besides the coverage information, it also contains the complexity metrics for each function. The format is described [here](https://github.com/cobertura/cobertura/blob/master/cobertura/src/site/htdocs/xml/coverage-04.dtd). ## Cyclomatic Complexity vs Cognitive Complexity @@ -110,7 +110,7 @@ Cognitive Complexity aims to produce a measurement that will correlate more clos ### Summary -In summary, while Cyclomatic Complexity is a measure of the structural complexity of a program, Cognitive Complexity is a measure of how difficult a program is to understand by a human reader. +In summary, while Cyclomatic Complexity is a measure of the structural complexity of a program, Cognitive Complexity is a measure of how difficult a program is to understand by a human reader. Both are useful, but they serve different purposes and can lead to different conclusions about the code's quality. ## Others @@ -119,4 +119,4 @@ So far we are aware about two other projects that do something similar: * [gocover-cobertura](https://github.com/boumenot/gocover-cobertura) However, both of them focus on the coverage part and take over a big downsides of the `go test -coverprofile` command. -Further this project adds complexity metrics, more options to determine coverage, and branch coverage. \ No newline at end of file +Further this project adds complexity metrics, more options to determine coverage, and branch coverage. diff --git a/pkg/source/source.go b/pkg/source/source.go index 303aafb..1c1b939 100644 --- a/pkg/source/source.go +++ b/pkg/source/source.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "go/token" "golang.org/x/tools/go/packages" "github.com/Fabianexe/gocoverageplus/pkg/entity" @@ -73,6 +74,38 @@ func LoadSources(path string, excludePaths []string) (*entity.Project, error) { countMethods++ className := getClassName(fun) methodsMap[className] = append(methodsMap[className], method) + } else if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.VAR { + for _, spec := range gen.Specs { + if valueSpec, ok := spec.(*ast.ValueSpec); ok { + for _, value := range valueSpec.Values { + if funcLit, ok := value.(*ast.FuncLit); ok && len(valueSpec.Names) > 0 { + method := &entity.Method{ + Name: valueSpec.Names[0].Name, + Body: funcLit.Body, + File: pkg.Fset.File(value.Pos()), + } + + // start after the function declaration + startLine := pkg.Fset.Position(funcLit.Body.Lbrace).Line + 1 + endLine := pkg.Fset.Position(funcLit.End()).Line + if startLine >= endLine { + continue + } + + bV := &branchVisitor{ + fset: pkg.Fset, + } + + ast.Walk(bV, valueSpec) + + method.Tree = bV.getTree() + + countMethods++ + methodsMap["-"] = append(methodsMap["-"], method) + } + } + } + } } } From d50825df76979a055e23f5c2aed08bd1177760a8 Mon Sep 17 00:00:00 2001 From: Robert Sworder Date: Tue, 17 Mar 2026 15:59:09 +0000 Subject: [PATCH 2/3] Make modules work --- README.md | 20 +++++++++++++------- go.mod | 2 +- main.go | 2 +- pkg/cleaner/cleaner.go | 2 +- pkg/cleaner/custom_if.go | 2 +- pkg/cleaner/err_if.go | 2 +- pkg/cleaner/generated.go | 2 +- pkg/cleaner/none_code.go | 2 +- pkg/commands/commands.go | 12 ++++++------ pkg/complexity/complexity.go | 2 +- pkg/coverage/coverage.go | 2 +- pkg/source/branch.go | 2 +- pkg/source/source.go | 2 +- pkg/writer/converter.go | 2 +- pkg/writer/writer.go | 2 +- 15 files changed, 32 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 53fddad..e1be78a 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,15 @@ Also, can write cobertura format. Including complexity metrics and branch covera ## Installation Simple install it by `go install`: ``` +go install github.com/thatcode/gocoverageplus@latest +``` + +Note: this is a fork of [Fabianexe/gocoverageplus](https://github.com/Fabianexe/gocoverageplus). To install the original: +``` go install github.com/Fabianexe/gocoverageplus@latest ``` + ## Usage `gocoverageplus` can be run without any arguments (fallbacking to defaults). However, it needs a config file in json format (See config section). @@ -43,12 +49,12 @@ The config file is a json file with the following structure: } } ``` -As output formats, you can choose between `cobertura` and `textfmt`. The first is described in the cobertura section and +As output formats, you can choose between `cobertura` and `textfmt`. The first is described in the cobertura section and the second is the default go coverage format. -Complexity only apply for the `cobertura` format. The complexity type can be either `cognitive` or `cyclomatic`. -The difference between these metrics is described in the cobertura section. +Complexity only apply for the `cobertura` format. The complexity type can be either `cognitive` or `cyclomatic`. +The difference between these metrics is described in the cobertura section. -The `SourcePath` is given relative to the working directory (starting with a `./`) or as absolute path. +The `SourcePath` is given relative to the working directory (starting with a `./`) or as absolute path. The `ExcludePaths` are paths that should be excluded from the coverage report. If a directory is in the exclude list, all files in this directory or subdirectory are excluded. They are given relative to the `SourcePath`. There are no white card or regex matching here. Thus, If you want to execlude more than one directory, you have to add them all to the list. The example above would exclude all files in the `vendor` directory in the working directory. @@ -85,7 +91,7 @@ You can activate these filters by using the corresponding config values. # Cobertura Format The cobertura format is a widely used format for coverage reports. It is supported by many tools like Jenkins. -It is an XML format that contains the coverage information for each file and package. +It is an XML format that contains the coverage information for each file and package. Besides the coverage information, it also contains the complexity metrics for each function. The format is described [here](https://github.com/cobertura/cobertura/blob/master/cobertura/src/site/htdocs/xml/coverage-04.dtd). ## Cyclomatic Complexity vs Cognitive Complexity @@ -110,7 +116,7 @@ Cognitive Complexity aims to produce a measurement that will correlate more clos ### Summary -In summary, while Cyclomatic Complexity is a measure of the structural complexity of a program, Cognitive Complexity is a measure of how difficult a program is to understand by a human reader. +In summary, while Cyclomatic Complexity is a measure of the structural complexity of a program, Cognitive Complexity is a measure of how difficult a program is to understand by a human reader. Both are useful, but they serve different purposes and can lead to different conclusions about the code's quality. ## Others @@ -119,4 +125,4 @@ So far we are aware about two other projects that do something similar: * [gocover-cobertura](https://github.com/boumenot/gocover-cobertura) However, both of them focus on the coverage part and take over a big downsides of the `go test -coverprofile` command. -Further this project adds complexity metrics, more options to determine coverage, and branch coverage. \ No newline at end of file +Further this project adds complexity metrics, more options to determine coverage, and branch coverage. diff --git a/go.mod b/go.mod index 6badbbf..f43f95c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/Fabianexe/gocoverageplus +module github.com/thatcode/gocoverageplus go 1.22.0 diff --git a/main.go b/main.go index 0173d76..89cfe9c 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/Fabianexe/gocoverageplus/pkg/commands" + "github.com/thatcode/gocoverageplus/pkg/commands" ) func main() { diff --git a/pkg/cleaner/cleaner.go b/pkg/cleaner/cleaner.go index e0ea8da..621f457 100644 --- a/pkg/cleaner/cleaner.go +++ b/pkg/cleaner/cleaner.go @@ -2,7 +2,7 @@ package cleaner import ( - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) // CleanData cleans the package data diff --git a/pkg/cleaner/custom_if.go b/pkg/cleaner/custom_if.go index d30aac9..88afacf 100644 --- a/pkg/cleaner/custom_if.go +++ b/pkg/cleaner/custom_if.go @@ -6,7 +6,7 @@ import ( "log/slog" "slices" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) // cleanErrorIf removes all error if statements from the package data diff --git a/pkg/cleaner/err_if.go b/pkg/cleaner/err_if.go index 3e0a354..c0f955f 100644 --- a/pkg/cleaner/err_if.go +++ b/pkg/cleaner/err_if.go @@ -5,7 +5,7 @@ import ( "go/token" "log/slog" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) // cleanErrorIf removes all error if statements from the package data diff --git a/pkg/cleaner/generated.go b/pkg/cleaner/generated.go index 47c7dd5..0516677 100644 --- a/pkg/cleaner/generated.go +++ b/pkg/cleaner/generated.go @@ -4,7 +4,7 @@ import ( "go/ast" "log/slog" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) func cleanGeneratedFiles(project *entity.Project) *entity.Project { diff --git a/pkg/cleaner/none_code.go b/pkg/cleaner/none_code.go index 984b420..e2cadba 100644 --- a/pkg/cleaner/none_code.go +++ b/pkg/cleaner/none_code.go @@ -5,7 +5,7 @@ import ( "go/token" "log/slog" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) func cleanNoneCodeLines(project *entity.Project) *entity.Project { diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index 7875ed7..c439336 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -9,12 +9,12 @@ import ( "github.com/spf13/cobra" - "github.com/Fabianexe/gocoverageplus/pkg/cleaner" - "github.com/Fabianexe/gocoverageplus/pkg/complexity" - "github.com/Fabianexe/gocoverageplus/pkg/config" - "github.com/Fabianexe/gocoverageplus/pkg/coverage" - "github.com/Fabianexe/gocoverageplus/pkg/source" - "github.com/Fabianexe/gocoverageplus/pkg/writer" + "github.com/thatcode/gocoverageplus/pkg/cleaner" + "github.com/thatcode/gocoverageplus/pkg/complexity" + "github.com/thatcode/gocoverageplus/pkg/config" + "github.com/thatcode/gocoverageplus/pkg/coverage" + "github.com/thatcode/gocoverageplus/pkg/source" + "github.com/thatcode/gocoverageplus/pkg/writer" ) func RootCommand() { diff --git a/pkg/complexity/complexity.go b/pkg/complexity/complexity.go index e13f2d7..779db0c 100644 --- a/pkg/complexity/complexity.go +++ b/pkg/complexity/complexity.go @@ -4,7 +4,7 @@ package complexity import ( "log/slog" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) // AddComplexity adds complexity metrics to the packages diff --git a/pkg/coverage/coverage.go b/pkg/coverage/coverage.go index faf5ef8..80c9b6d 100644 --- a/pkg/coverage/coverage.go +++ b/pkg/coverage/coverage.go @@ -7,7 +7,7 @@ import ( "golang.org/x/tools/cover" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) // LoadCoverage loads the coverage data from the given file diff --git a/pkg/source/branch.go b/pkg/source/branch.go index 84fa46d..f049489 100644 --- a/pkg/source/branch.go +++ b/pkg/source/branch.go @@ -4,7 +4,7 @@ import ( "go/ast" "go/token" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) type branchVisitor struct { diff --git a/pkg/source/source.go b/pkg/source/source.go index 303aafb..03fc0a0 100644 --- a/pkg/source/source.go +++ b/pkg/source/source.go @@ -11,7 +11,7 @@ import ( "golang.org/x/tools/go/packages" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) func LoadSources(path string, excludePaths []string) (*entity.Project, error) { diff --git a/pkg/writer/converter.go b/pkg/writer/converter.go index 6c5bd6b..6409a76 100644 --- a/pkg/writer/converter.go +++ b/pkg/writer/converter.go @@ -3,7 +3,7 @@ package writer import ( "strconv" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) func ConvertToCobertura(path string, project *entity.Project) *Coverage { diff --git a/pkg/writer/writer.go b/pkg/writer/writer.go index 0f07d36..dc6e56d 100644 --- a/pkg/writer/writer.go +++ b/pkg/writer/writer.go @@ -9,7 +9,7 @@ import ( "os" "path" - "github.com/Fabianexe/gocoverageplus/pkg/entity" + "github.com/thatcode/gocoverageplus/pkg/entity" ) func WriteXML(path string, project *entity.Project, outPath string) error { From 7c5606488c2a86cc174bf8cef3159d722d66123b Mon Sep 17 00:00:00 2001 From: Robert Sworder Date: Wed, 18 Mar 2026 13:33:51 +0000 Subject: [PATCH 3/3] Revert "Make modules work" This reverts commit d50825df76979a055e23f5c2aed08bd1177760a8. --- README.md | 6 ------ go.mod | 2 +- main.go | 2 +- pkg/cleaner/cleaner.go | 2 +- pkg/cleaner/custom_if.go | 2 +- pkg/cleaner/err_if.go | 2 +- pkg/cleaner/generated.go | 2 +- pkg/cleaner/none_code.go | 2 +- pkg/commands/commands.go | 12 ++++++------ pkg/complexity/complexity.go | 2 +- pkg/coverage/coverage.go | 2 +- pkg/source/branch.go | 2 +- pkg/source/source.go | 2 +- pkg/writer/converter.go | 2 +- pkg/writer/writer.go | 2 +- 15 files changed, 19 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e3a2d66..2e6dd36 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,9 @@ Also, can write cobertura format. Including complexity metrics and branch covera ## Installation Simple install it by `go install`: ``` -go install github.com/thatcode/gocoverageplus@latest -``` - -Note: this is a fork of [Fabianexe/gocoverageplus](https://github.com/Fabianexe/gocoverageplus). To install the original: -``` go install github.com/Fabianexe/gocoverageplus@latest ``` - ## Usage `gocoverageplus` can be run without any arguments (fallbacking to defaults). However, it needs a config file in json format (See config section). diff --git a/go.mod b/go.mod index f43f95c..6badbbf 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/thatcode/gocoverageplus +module github.com/Fabianexe/gocoverageplus go 1.22.0 diff --git a/main.go b/main.go index 89cfe9c..0173d76 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/thatcode/gocoverageplus/pkg/commands" + "github.com/Fabianexe/gocoverageplus/pkg/commands" ) func main() { diff --git a/pkg/cleaner/cleaner.go b/pkg/cleaner/cleaner.go index 621f457..e0ea8da 100644 --- a/pkg/cleaner/cleaner.go +++ b/pkg/cleaner/cleaner.go @@ -2,7 +2,7 @@ package cleaner import ( - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) // CleanData cleans the package data diff --git a/pkg/cleaner/custom_if.go b/pkg/cleaner/custom_if.go index 88afacf..d30aac9 100644 --- a/pkg/cleaner/custom_if.go +++ b/pkg/cleaner/custom_if.go @@ -6,7 +6,7 @@ import ( "log/slog" "slices" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) // cleanErrorIf removes all error if statements from the package data diff --git a/pkg/cleaner/err_if.go b/pkg/cleaner/err_if.go index c0f955f..3e0a354 100644 --- a/pkg/cleaner/err_if.go +++ b/pkg/cleaner/err_if.go @@ -5,7 +5,7 @@ import ( "go/token" "log/slog" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) // cleanErrorIf removes all error if statements from the package data diff --git a/pkg/cleaner/generated.go b/pkg/cleaner/generated.go index 0516677..47c7dd5 100644 --- a/pkg/cleaner/generated.go +++ b/pkg/cleaner/generated.go @@ -4,7 +4,7 @@ import ( "go/ast" "log/slog" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) func cleanGeneratedFiles(project *entity.Project) *entity.Project { diff --git a/pkg/cleaner/none_code.go b/pkg/cleaner/none_code.go index e2cadba..984b420 100644 --- a/pkg/cleaner/none_code.go +++ b/pkg/cleaner/none_code.go @@ -5,7 +5,7 @@ import ( "go/token" "log/slog" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) func cleanNoneCodeLines(project *entity.Project) *entity.Project { diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index c439336..7875ed7 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -9,12 +9,12 @@ import ( "github.com/spf13/cobra" - "github.com/thatcode/gocoverageplus/pkg/cleaner" - "github.com/thatcode/gocoverageplus/pkg/complexity" - "github.com/thatcode/gocoverageplus/pkg/config" - "github.com/thatcode/gocoverageplus/pkg/coverage" - "github.com/thatcode/gocoverageplus/pkg/source" - "github.com/thatcode/gocoverageplus/pkg/writer" + "github.com/Fabianexe/gocoverageplus/pkg/cleaner" + "github.com/Fabianexe/gocoverageplus/pkg/complexity" + "github.com/Fabianexe/gocoverageplus/pkg/config" + "github.com/Fabianexe/gocoverageplus/pkg/coverage" + "github.com/Fabianexe/gocoverageplus/pkg/source" + "github.com/Fabianexe/gocoverageplus/pkg/writer" ) func RootCommand() { diff --git a/pkg/complexity/complexity.go b/pkg/complexity/complexity.go index 779db0c..e13f2d7 100644 --- a/pkg/complexity/complexity.go +++ b/pkg/complexity/complexity.go @@ -4,7 +4,7 @@ package complexity import ( "log/slog" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) // AddComplexity adds complexity metrics to the packages diff --git a/pkg/coverage/coverage.go b/pkg/coverage/coverage.go index 80c9b6d..faf5ef8 100644 --- a/pkg/coverage/coverage.go +++ b/pkg/coverage/coverage.go @@ -7,7 +7,7 @@ import ( "golang.org/x/tools/cover" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) // LoadCoverage loads the coverage data from the given file diff --git a/pkg/source/branch.go b/pkg/source/branch.go index f049489..84fa46d 100644 --- a/pkg/source/branch.go +++ b/pkg/source/branch.go @@ -4,7 +4,7 @@ import ( "go/ast" "go/token" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) type branchVisitor struct { diff --git a/pkg/source/source.go b/pkg/source/source.go index 532d7cf..1c1b939 100644 --- a/pkg/source/source.go +++ b/pkg/source/source.go @@ -12,7 +12,7 @@ import ( "go/token" "golang.org/x/tools/go/packages" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) func LoadSources(path string, excludePaths []string) (*entity.Project, error) { diff --git a/pkg/writer/converter.go b/pkg/writer/converter.go index 6409a76..6c5bd6b 100644 --- a/pkg/writer/converter.go +++ b/pkg/writer/converter.go @@ -3,7 +3,7 @@ package writer import ( "strconv" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) func ConvertToCobertura(path string, project *entity.Project) *Coverage { diff --git a/pkg/writer/writer.go b/pkg/writer/writer.go index dc6e56d..0f07d36 100644 --- a/pkg/writer/writer.go +++ b/pkg/writer/writer.go @@ -9,7 +9,7 @@ import ( "os" "path" - "github.com/thatcode/gocoverageplus/pkg/entity" + "github.com/Fabianexe/gocoverageplus/pkg/entity" ) func WriteXML(path string, project *entity.Project, outPath string) error {