How the go-modern-guidelines Tool Handles Go 1.22's `range over int` Feature

The JetBrains go-modern-guidelines CLI automatically detects Go 1.22+ environments and recommends replacing classic for i := 0; i < n; i++ loops with the concise for i := range n syntax, while explicitly preserving traditional for-loops for non-zero starts, custom steps, and mutable bounds.

Go 1.22 introduced the range over int feature, allowing zero-based count loops without explicit initialization, condition, or increment statements. The JetBrains/go-modern-guidelines repository provides a command-line tool that analyzes your Go version through internal/goversion/goversion.go and applies the range_over_int guideline from internal/guidelines/guidelines.json to suggest modernizations that reduce boilerplate while maintaining correctness.

Version Detection and Guideline Activation

The tool determines whether your codebase supports the range over int syntax by evaluating the target Go version against the 1.22 threshold. In internal/goversion/goversion.go, the Compare function checks the current version context to filter which modernization rules apply.

When the detected version is greater than or equal to 1.22, the engine activates the specific guideline defined at lines 630-648 of internal/guidelines/guidelines.json. This version-gated approach ensures the tool only suggests language features compatible with your toolchain, preventing invalid recommendations in older projects.

How the Guideline Engine Processes Loop Patterns

The CLI loads and renders recommendations through a pipeline defined in internal/guidelines/guidelines.go. The mustLoadModernGoGuidelines function parses the JSON guideline definitions, including the range_over_int rule marked with Critical impact, while writeExamples (lines 86-100) formats the human-readable output shown during list or explain commands.

When invoked through main.go, the tool executes this workflow:

  1. Version Resolution: Calls goversion.Compare to validate Go 1.22+ support.
  2. Guideline Filtering: Loads applicable rules from internal/guidelines/guidelines.json.
  3. Pattern Analysis: Identifies count loops that match the zero-based, single-step criteria.
  4. Rendering: Generates before/after examples via writeExamples, including explicit warnings for exception cases.

Recognized Loop Patterns and Exceptions

The tool categorizes count loops into transformations and explicit exceptions based on semantic requirements.

Bold Transformation: Loops iterating from 0 to n-1 with a step of 1 should use for i := range n.

This pattern eliminates three clauses of boilerplate while preserving identical behavior. The guideline applies when iterating over slice lengths or numeric constants where the index starts at zero and increments uniformly.

Preserved: Classic For-Loop Exceptions

The tool explicitly rejects range over int for three specific scenarios:

  • Non-zero start: for i := 5; i < len(items); i++ must remain classic because range always initializes at 0.
  • Custom step increments: for i := 0; i < n; i += 2 requires traditional syntax since range cannot express step values other than 1.
  • Mutable bounds: Loops where len(slice) changes during iteration must use the classic form to re-evaluate the condition each cycle, whereas range captures the bound once at initialization.

Code Transformation Examples

The following patterns demonstrate how the tool differentiates between recommended modernizations and legacy preservation.

Zero-Based Index with Length (Transform)

// Before: Classic for-loop
for i := 0; i < len(items); i++ {
    process(items[i])
}

// After: Range over int
for i := range len(items) {
    process(items[i])
}

Non-Zero Start Index (Preserve)

// Keep classic form: starts at 5, not 0
for i := 5; i < len(items); i++ {
    process(items[i])
}

Custom Step Increment (Preserve)

// Keep classic form: step size of 2
for i := 0; i < len(items); i += 2 {
    process(items[i])
}

Mutable Bound During Iteration (Preserve)

// Keep classic form: bound changes during iteration
for i := 0; i < len(items); i++ {
    if shouldDrop(i) {
        items = items[:len(items)-1] // mutates length
    }
    process(items[i])
}

Fixed Numeric Range (Transform)

// Transform to range over int
for i := range 10 {
    fmt.Println(i) // prints 0..9
}

Summary

  • The range over int guideline activates only when goversion.Compare in internal/goversion/goversion.go detects Go 1.22 or later.
  • Guideline definitions are stored in internal/guidelines/guidelines.json (lines 630-648) with Critical priority due to significant boilerplate reduction.
  • mustLoadModernGoGuidelines and writeExamples in internal/guidelines/guidelines.go (lines 86-100) handle JSON parsing and CLI output formatting.
  • The tool recommends for i := range n exclusively for zero-based, single-step loops with immutable bounds.
  • Classic for-loops must be preserved for non-zero starts, custom increments (e.g., i += 2), and scenarios where the iteration bound mutates during execution.

Frequently Asked Questions

Can the tool automatically refactor my code, or does it only provide recommendations?

The JetBrains go-modern-guidelines CLI currently operates as a linter and educator rather than an automated refactoring tool. When you run the list or explain commands, the tool analyzes your Go version via internal/goversion/goversion.go and displays specific guideline details rendered by writeExamples. You must manually apply transformations based on these recommendations.

Why does the tool reject range over int for loops that modify the slice length during iteration?

The range expression evaluates the bound exactly once at loop initialization. If your code reslices the array or appends to the slice within the loop body, the range uses the stale initial length, potentially causing missed elements or index out-of-bounds errors. The traditional for i := 0; i < len(s); i++ form re-evaluates len(s) on each iteration, making it the only safe choice for mutable-bound loops.

How does the tool handle Go versions earlier than 1.22?

For projects targeting Go versions below 1.22, the goversion.Compare function filters out the range_over_int guideline entirely. The guideline record in internal/guidelines/guidelines.json specifies the minimum version constraint, so the rendering engine excludes this recommendation from output, preventing the suggestion of unsupported language features in legacy codebases.

Does the tool support range over other integer types like int64?

Based on the guideline implementation in internal/guidelines/guidelines.json, the tool specifically targets the standard int type used in length expressions like len(slice). While Go 1.22's range over int supports any integer type, the tool's pattern recognition focuses on the most common idioms involving len() results and explicit integer constants where int is the default type.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →