# How go-modern-guidelines Detects a Project's Go Version from go.mod

> Learn how go-modern-guidelines detects your project's Go version by parsing the go directive from go.mod or go.work files. Understand the process step-by-step.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: how-to-guide
- Published: 2026-08-30

---

**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`](https://github.com/JetBrains/go-modern-guidelines/blob/main/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:

```go
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:

```bash
$ go run .
Detected Go version: 1.22

```

To override automatic detection and force a specific version:

```go
ver, _ := goversion.Resolve("", "1.23", "devel")
fmt.Println(ver) // → 1.23

```

## Summary

- **go-modern-guidelines** detects versions via [`internal/goversion/goversion.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/goversion/goversion.go), which walks up the directory tree to find `go.mod` or `go.work`.
- The **`Resolve`** function prioritizes explicit CLI flags, then module files, then the local Go toolchain.
- The **`parseGoDirective`** function uses `golang.org/x/mod/modfile` to extract the `go` directive from both module and workspace files.
- **`normalizeGoVersion`** converts raw version strings into a consistent `major.minor` format (e.g., `"1.22"`).
- If no module file exists, the tool falls back to executing `go env GOVERSION` and 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`](https://github.com/JetBrains/go-modern-guidelines/blob/main/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.