How go-modern-guidelines Detects a Project's Go Version from go.mod
The tool discovers the Go version by parsing the go directive from the nearest go.mod or go.work file, walking upward through the directory tree, and normalizing the result to a canonical major.minor string.
The go-modern-guidelines repository automates Go version detection to enforce modern coding standards. According to the JetBrains/go-modern-guidelines source code, the detection logic resides entirely within the internal/goversion package, which handles file system traversal, module file parsing, and version normalization.
The Detection Pipeline in internal/goversion/goversion.go
The core implementation lives in internal/goversion/goversion.go, which exports a single public entry point and several internal helpers. The detection flow follows a strict hierarchy: explicit CLI flags take precedence, followed by module file discovery, with a final fallback to the local Go toolchain.
Entry Point: The Resolve Function
The Resolve function serves as the primary API. It accepts three parameters: filePath, goVersion, and develVersion. As implemented in the CLI wiring, if an explicit version is supplied via the --go-version flag, Resolve immediately normalizes and returns that value. Otherwise, it delegates to resolveGoVersionFromPath when a directory or file path is provided, or falls back to the local toolchain when no path is given.
Locating the Module File with resolveGoVersionFromPath
When the caller provides a path, resolveGoVersionFromPath first checks if the path points directly to a module file (go.mod or go.work). If not, it invokes findUp to walk upward from the target directory toward the file system root, searching for the nearest module file. This upward traversal ensures that nested packages correctly identify their parent module's declared version. Once located, the function calls parseGoDirective to extract the version string.
Parsing the go Directive
The parseGoDirective function reads the discovered file using os.ReadFile and dispatches to the appropriate parser from golang.org/x/mod/modfile. For standard modules, it uses modfile.Parse to produce a modfile.File structure; for workspace files, it uses modfile.ParseWork. In both cases, the function extracts the Version field from the Go struct within the parsed object. This raw string—often formatted as "1.22" or "go1.22.3"—requires normalization before consumption.
Normalizing the Version String
Raw version strings are passed to normalizeGoVersion, which applies the goVersionInText regex to isolate the first major.minor token. The function parses the major and minor components into integers, then formats them back into a standardized string (e.g., "1.22"). This normalization ensures consistent comparison logic regardless of whether the source file contains a bare version, a prefixed string, or a full patch release identifier.
Fallback to the Go Toolchain
If the directory walk fails to locate a go.mod or go.work file, the pipeline executes resolveGoToolVersion. This helper runs the command go env GOVERSION, captures the output, and normalizes it through the same pipeline. This guarantees that the tool always returns a valid version string, even when executed outside a Go module hierarchy.
Practical Usage Examples
To detect the Go version for the module in the current working directory:
package main
import (
"fmt"
"github.com/JetBrains/go-modern-guidelines/internal/goversion"
)
func main() {
version, err := goversion.Resolve(".", "", "devel")
if err != nil {
panic(err)
}
fmt.Println("Detected Go version:", version)
}
Running the above produces:
$ go run .
Detected Go version: 1.22
To override automatic detection and force a specific version:
ver, _ := goversion.Resolve("", "1.23", "devel")
fmt.Println(ver) // → 1.23
Summary
- go-modern-guidelines detects versions via
internal/goversion/goversion.go, which walks up the directory tree to findgo.modorgo.work. - The
Resolvefunction prioritizes explicit CLI flags, then module files, then the local Go toolchain. - The
parseGoDirectivefunction usesgolang.org/x/mod/modfileto extract thegodirective from both module and workspace files. normalizeGoVersionconverts raw version strings into a consistentmajor.minorformat (e.g.,"1.22").- If no module file exists, the tool falls back to executing
go env GOVERSIONand parsing the result.
Frequently Asked Questions
How does go-modern-guidelines handle go.work files?
The tool treats go.work files identically to go.mod files. In internal/goversion/goversion.go, the parseGoDirective function checks the file name and dispatches to modfile.ParseWork for workspace files, extracting the go directive from the resulting structure just as it would for a standard module.
What happens if no go.mod file is found?
If the upward directory walk fails to locate a module or workspace file, the detection pipeline invokes resolveGoToolVersion. This executes go env GOVERSION to retrieve the local Go toolchain's version, normalizes it, and returns that value as the effective version.
Can I override the detected Go version?
Yes. When calling goversion.Resolve, provide a non-empty string for the goVersion parameter (typically forwarded from the --go-version CLI flag). The function returns this value immediately after normalization, bypassing all file system detection and toolchain queries.
What version format does the tool return?
The tool always returns a normalized string in major.minor format (e.g., "1.21", "1.22"). The normalizeGoVersion function strips patch numbers, release candidate suffixes, and "go" prefixes to ensure consistent, comparable version identifiers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →