or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

dirhash.mdgosumcheck.mdindex.mdmodfile.mdmodule.mdnote.mdsemver.mdstorage.mdsumdb.mdtlog.mdzip.md
tile.json

modfile.mddocs/

modfile - go.mod and go.work File Parser

Package modfile implements a parser and formatter for go.mod and go.work files.

Import

import "golang.org/x/mod/modfile"

Overview

The modfile package provides functionality to parse, manipulate, and format Go module configuration files (go.mod and go.work). The go.mod syntax is described in the official documentation.

Key features:

  • Parsing: Parse and ParseLax functions parse go.mod files and return an abstract syntax tree
  • Manipulation: The File struct has methods like AddNewRequire and DropReplace for programmatic editing
  • Formatting: Format function formats a File back to bytes for writing
  • Workspace Support: ParseWork and WorkFile handle go.work files

The ParseLax function ignores unknown statements and may be used to parse go.mod files developed with newer versions of Go, ensuring forward compatibility.

Variables

var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)

Regular expression for validating Go version strings. Matches versions like "1.18", "1.19.2", "1.20rc1".

var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`)

Regular expression for toolchain names. Toolchains must be named beginning with go1, like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. This regexp is intentionally loose for forward compatibility.

Types

File

type File struct {
    Module    *Module
    Go        *Go
    Toolchain *Toolchain
    Godebug   []*Godebug
    Require   []*Require
    Exclude   []*Exclude
    Replace   []*Replace
    Retract   []*Retract
    Tool      []*Tool
    Ignore    []*Ignore

    Syntax *FileSyntax
}

A File is the parsed, interpreted form of a go.mod file. It contains all the directives from the file in structured form.

File Methods - Module Statement

func (f *File) AddModuleStmt(path string) error

Adds or updates the module statement.

Example:

f.AddModuleStmt("github.com/user/project")

File Methods - Go Version

func (f *File) AddGoStmt(version string) error

Adds or updates the go statement.

func (f *File) DropGoStmt()

Deletes the go statement from the file.

Example:

f.AddGoStmt("1.21")

File Methods - Toolchain

func (f *File) AddToolchainStmt(name string) error

Adds or updates the toolchain statement.

func (f *File) DropToolchainStmt()

Deletes the toolchain statement from the file.

Example:

f.AddToolchainStmt("go1.21.0")

File Methods - Require Directives

func (f *File) AddRequire(path, vers string) error

Sets the first require line for path to version vers, preserving any existing comments for that line and removing all other lines for path. If no line currently exists for path, AddRequire adds a new line at the end of the last require block.

func (f *File) AddNewRequire(path, vers string, indirect bool)

Adds a new require line for path at version vers at the end of the last require block, regardless of any existing require lines for path.

func (f *File) SetRequire(req []*Require)

Updates the requirements of f to contain exactly req, preserving the existing block structure and line comment contents (except for 'indirect' markings) for the first requirement on each named module path.

The Syntax field is ignored for the requirements in req. Any requirements not already present in the file are added to the block containing the last require line.

The requirements in req must specify at most one distinct version for each module path. If any existing requirements may be removed, the caller should call File.Cleanup after all edits are complete.

func (f *File) SetRequireSeparateIndirect(req []*Require)

Updates the requirements of f to contain the given requirements. Like SetRequire, it adds requirements for new paths, updates versions and "// indirect" comments, and deletes requirements not in req.

As its name suggests, SetRequireSeparateIndirect puts direct and indirect requirements into two separate blocks. It may move requirements between blocks when their indirect markings change, but won't move requirements from other blocks (especially blocks with comments).

If the file initially has one uncommented block of requirements, SetRequireSeparateIndirect will split it into direct-only and indirect-only blocks, aiding in the transition to separate blocks.

func (f *File) DropRequire(path string) error

Removes require statements for the given path.

Example:

f.AddRequire("golang.org/x/text", "v0.3.7")
f.AddNewRequire("golang.org/x/tools", "v0.1.0", true) // indirect
f.DropRequire("golang.org/x/old")

File Methods - Exclude Directives

func (f *File) AddExclude(path, vers string) error

Adds an exclude statement to the mod file. Errors if the provided version is not a canonical version string.

func (f *File) DropExclude(path, vers string) error

Removes an exclude statement.

Example:

f.AddExclude("example.com/bad/module", "v1.0.0")

File Methods - Replace Directives

func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error

Adds or updates a replace statement.

func (f *File) DropReplace(oldPath, oldVers string) error

Removes a replace statement.

Example:

// Replace with local path
f.AddReplace("example.com/pkg", "", "./local/pkg", "")

// Replace specific version
f.AddReplace("example.com/pkg", "v1.0.0", "example.com/fork", "v1.0.1")

File Methods - Retract Directives

func (f *File) AddRetract(vi VersionInterval, rationale string) error

Adds a retract statement to the mod file. Errors if the provided version interval does not consist of canonical version strings.

func (f *File) DropRetract(vi VersionInterval) error

Removes a retract statement.

Example:

// Retract a single version
vi := modfile.VersionInterval{Low: "v1.0.0", High: "v1.0.0"}
f.AddRetract(vi, "Published accidentally")

// Retract a range
vi = modfile.VersionInterval{Low: "v1.0.0", High: "v1.5.0"}
f.AddRetract(vi, "Security issues")

File Methods - Godebug Directives

func (f *File) AddGodebug(key, value string) error

Sets the first godebug line for key to value, preserving any existing comments for that line and removing all other godebug lines for key. If no line currently exists for key, AddGodebug adds a new line at the end of the last godebug block.

func (f *File) DropGodebug(key string) error

Removes godebug statements for the given key.

Example:

f.AddGodebug("panicnil", "1")

File Methods - Tool Directives

func (f *File) AddTool(path string) error

Adds a new tool directive with the given path. It does nothing if the tool line already exists.

func (f *File) DropTool(path string) error

Removes a tool directive with the given path. It does nothing if no such tool directive exists.

Example:

f.AddTool("golang.org/x/tools/cmd/stringer")

File Methods - Ignore Directives

func (f *File) AddIgnore(path string) error

Adds a new ignore directive with the given path. It does nothing if the ignore line already exists.

func (f *File) DropIgnore(path string) error

Removes an ignore directive with the given path. It does nothing if no such ignore directive exists.

Example:

f.AddIgnore("example.com/deprecated/module")

File Methods - Comments

func (f *File) AddComment(text string)

Adds a comment to the file.

Example:

f.AddComment("// This is an auto-generated file")

File Methods - Formatting and Cleanup

func (f *File) Format() ([]byte, error)

Formats the file as bytes.

func (f *File) Cleanup()

Cleans up the file f after any edit operations. To avoid quadratic behavior, modifications like File.DropRequire clear the entry but do not remove it from the slice. Cleanup cleans out all the cleared entries.

func (f *File) SortBlocks()

Sorts the blocks in the file.

Example:

f.SortBlocks()
f.Cleanup()
data, err := f.Format()
if err != nil {
    log.Fatal(err)
}
os.WriteFile("go.mod", data, 0644)

WorkFile

type WorkFile struct {
    Go        *Go
    Toolchain *Toolchain
    Godebug   []*Godebug
    Use       []*Use
    Replace   []*Replace

    Syntax *FileSyntax
}

A WorkFile is the parsed, interpreted form of a go.work file.

WorkFile Methods - Go Version

func (f *WorkFile) AddGoStmt(version string) error

Adds or updates the go statement.

func (f *WorkFile) DropGoStmt()

Deletes the go statement from the file.

WorkFile Methods - Toolchain

func (f *WorkFile) AddToolchainStmt(name string) error

Adds or updates the toolchain statement.

func (f *WorkFile) DropToolchainStmt()

Deletes the toolchain statement from the file.

WorkFile Methods - Use Directives

func (f *WorkFile) AddUse(diskPath, modulePath string) error

Adds or updates a use directive.

func (f *WorkFile) AddNewUse(diskPath, modulePath string)

Adds a new use directive.

func (f *WorkFile) SetUse(dirs []*Use)

Sets the use directives.

func (f *WorkFile) DropUse(path string) error

Removes a use directive.

Example:

f.AddUse("./module1", "example.com/module1")
f.AddUse("./module2", "example.com/module2")

WorkFile Methods - Replace Directives

func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error

Adds or updates a replace statement.

func (f *WorkFile) DropReplace(oldPath, oldVers string) error

Removes a replace statement.

WorkFile Methods - Godebug Directives

func (f *WorkFile) AddGodebug(key, value string) error

Sets the first godebug line for key to value, preserving any existing comments for that line and removing all other godebug lines for key. If no line currently exists for key, AddGodebug adds a new line at the end of the last godebug block.

func (f *WorkFile) DropGodebug(key string) error

Removes godebug statements for the given key.

WorkFile Methods - Cleanup

func (f *WorkFile) Cleanup()

Cleans up the file f after any edit operations. To avoid quadratic behavior, modifications clear the entry but do not remove it from the slice. Cleanup cleans out all the cleared entries.

func (f *WorkFile) SortBlocks()

Sorts the blocks in the file.

Directive Types

type Module struct {
    Mod        module.Version
    Deprecated string
    Syntax     *Line
}

A Module is the module statement.

type Go struct {
    Version string // "1.23"
    Syntax  *Line
}

A Go is the go statement.

type Toolchain struct {
    Name   string // "go1.21rc1"
    Syntax *Line
}

A Toolchain is the toolchain statement.

type Require struct {
    Mod      module.Version
    Indirect bool // has "// indirect" comment
    Syntax   *Line
}

A Require is a single require statement.

type Exclude struct {
    Mod    module.Version
    Syntax *Line
}

An Exclude is a single exclude statement.

type Replace struct {
    Old    module.Version
    New    module.Version
    Syntax *Line
}

A Replace is a single replace statement.

type Retract struct {
    VersionInterval
    Rationale string
    Syntax    *Line
}

A Retract is a single retract statement.

type Godebug struct {
    Key    string
    Value  string
    Syntax *Line
}

A Godebug is a single godebug key=value statement.

type Tool struct {
    Path   string
    Syntax *Line
}

A Tool is a single tool statement.

type Ignore struct {
    Path   string
    Syntax *Line
}

An Ignore is a single ignore statement.

type Use struct {
    Path       string // Use path of module.
    ModulePath string // Module path in the comment.
    Syntax     *Line
}

A Use is a single directory statement in a go.work file.

type VersionInterval struct {
    Low, High string
}

A VersionInterval represents a range of versions with upper and lower bounds. Intervals are closed: both bounds are included. When Low is equal to High, the interval may refer to a single version ('v1.2.3') or an interval ('[v1.2.3, v1.2.3]'); both have the same representation.

Syntax Tree Types

type FileSyntax struct {
    Name string // file path
    Comments
    Stmt []Expr
}

A FileSyntax represents an entire go.mod file.

FileSyntax Methods

func (x *FileSyntax) Span() (start, end Position)
func (x *FileSyntax) Cleanup()

Cleans up the file syntax x after any edit operations. To avoid quadratic behavior, (*Line).markRemoved marks the line as dead by setting line.Token = nil but does not remove it from the slice. After edits have all been indicated, calling Cleanup cleans out the dead lines.

type Expr interface {
    // Span returns the start and end position of the expression,
    // excluding leading or trailing comments.
    Span() (start, end Position)

    // Comment returns the comments attached to the expression.
    // This method would normally be named 'Comments' but that
    // would interfere with embedding a type of the same name.
    Comment() *Comments
}

An Expr represents an input element.

type Comments struct {
    Before []Comment // whole-line comments before this expression
    Suffix []Comment // end-of-line comments after this expression

    // For top-level expressions only, After lists whole-line
    // comments following the expression.
    After []Comment
}

Comments collects the comments associated with an expression.

Comments Methods

func (c *Comments) Comment() *Comments

Returns the receiver. This isn't useful by itself, but a Comments struct is embedded into all the expression implementation types, and this gives each of those a Comment method to satisfy the Expr interface.

type Comment struct {
    Start  Position
    Token  string // without trailing newline
    Suffix bool   // an end of line (not whole line) comment
}

A Comment represents a single // comment.

type CommentBlock struct {
    Comments
    Start Position
}

A CommentBlock represents a top-level block of comments separate from any rule.

CommentBlock Methods

func (x *CommentBlock) Span() (start, end Position)
type Line struct {
    Comments
    Start   Position
    Token   []string
    InBlock bool
    End     Position
}

A Line is a single line of tokens.

Line Methods

func (x *Line) Span() (start, end Position)
type LineBlock struct {
    Comments
    Start  Position
    LParen LParen
    Token  []string
    Line   []*Line
    RParen RParen
}

A LineBlock is a factored block of lines, like:

require (
    "x"
    "y"
)

LineBlock Methods

func (x *LineBlock) Span() (start, end Position)
type LParen struct {
    Comments
    Pos Position
}

An LParen represents the beginning of a parenthesized line block. It is a place to store suffix comments.

LParen Methods

func (x *LParen) Span() (start, end Position)
type RParen struct {
    Comments
    Pos Position
}

An RParen represents the end of a parenthesized line block. It is a place to store whole-line (before) comments.

RParen Methods

func (x *RParen) Span() (start, end Position)
type Position struct {
    Line     int // line in input (starting at 1)
    LineRune int // rune in line (starting at 1)
    Byte     int // byte in input (starting at 0)
}

A Position describes an arbitrary source position in a file, including the file, line, column, and byte offset.

Error Types

type Error struct {
    Filename string
    Pos      Position
    Verb     string
    ModPath  string
    Err      error
}

Error Methods

func (e *Error) Error() string
func (e *Error) Unwrap() error
type ErrorList []Error

ErrorList Methods

func (e ErrorList) Error() string

Function Types

type VersionFixer func(path, version string) (string, error)

A function type for fixing/canonicalizing version strings. Used when parsing to normalize version strings to their canonical form.

Functions

Parsing Functions

func Parse(file string, data []byte, fix VersionFixer) (*File, error)

Parses and returns a go.mod file.

Parameters:

  • file: The name of the file, used in positions and errors
  • data: The content of the file
  • fix: An optional function that canonicalizes module versions. If fix is nil, all module versions must be canonical (module.CanonicalVersion must return the same string)

Example:

data, err := os.ReadFile("go.mod")
if err != nil {
    log.Fatal(err)
}

f, err := modfile.Parse("go.mod", data, nil)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Module: %s\n", f.Module.Mod.Path)
func ParseLax(file string, data []byte, fix VersionFixer) (*File, error)

Like Parse but ignores unknown statements. It is used when parsing go.mod files other than the main module, under the theory that most statement types added in the future will only apply in the main module (like exclude and replace), and so older Go commands can simply ignore those statements when found in go.mod files in dependencies.

Example:

// Parse a go.mod from a dependency that might use newer syntax
f, err := modfile.ParseLax("go.mod", data, nil)
if err != nil {
    log.Fatal(err)
}
func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error)

Parses and returns a go.work file.

Parameters:

  • file: The name of the file, used in positions and errors
  • data: The content of the file
  • fix: An optional function that canonicalizes module versions

Example:

data, err := os.ReadFile("go.work")
if err != nil {
    log.Fatal(err)
}

wf, err := modfile.ParseWork("go.work", data, nil)
if err != nil {
    log.Fatal(err)
}

for _, use := range wf.Use {
    fmt.Printf("Use: %s\n", use.Path)
}

Formatting Functions

func Format(f *FileSyntax) []byte

Returns a go.mod file as a byte slice, formatted in standard style.

Example:

formatted := modfile.Format(f.Syntax)
os.WriteFile("go.mod", formatted, 0644)

Utility Functions

func ModulePath(mod []byte) string

Returns the module path from the gomod file text. If it cannot find a module path, it returns an empty string. It is tolerant of unrelated problems in the go.mod file.

Example:

data, _ := os.ReadFile("go.mod")
path := modfile.ModulePath(data)
fmt.Printf("Module path: %s\n", path)
func AutoQuote(s string) string

Returns s or, if quoting is required for s to appear in a go.mod, the quotation of s.

Example:

quoted := modfile.AutoQuote("path/with spaces")
fmt.Println(quoted) // Output: "path/with spaces"
func MustQuote(s string) bool

Reports whether s must be quoted in order to appear as a single token in a go.mod line.

Example:

if modfile.MustQuote("path with spaces") {
    fmt.Println("This path needs quoting")
}
func IsDirectoryPath(ns string) bool

Reports whether the given path should be interpreted as a directory path. Just like on the go command line, relative paths starting with a '.' or '..' path component and rooted paths are directory paths; the rest are module paths.

Example:

fmt.Println(modfile.IsDirectoryPath("./local"))     // true
fmt.Println(modfile.IsDirectoryPath("../other"))    // true
fmt.Println(modfile.IsDirectoryPath("/abs/path"))   // true
fmt.Println(modfile.IsDirectoryPath("example.com")) // false

Usage Examples

Parsing and Reading a go.mod File

package main

import (
    "fmt"
    "log"
    "os"

    "golang.org/x/mod/modfile"
)

func main() {
    data, err := os.ReadFile("go.mod")
    if err != nil {
        log.Fatal(err)
    }

    f, err := modfile.Parse("go.mod", data, nil)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Module: %s\n", f.Module.Mod.Path)
    if f.Go != nil {
        fmt.Printf("Go version: %s\n", f.Go.Version)
    }

    fmt.Println("\nRequirements:")
    for _, req := range f.Require {
        indirect := ""
        if req.Indirect {
            indirect = " // indirect"
        }
        fmt.Printf("  %s %s%s\n", req.Mod.Path, req.Mod.Version, indirect)
    }

    if len(f.Replace) > 0 {
        fmt.Println("\nReplacements:")
        for _, rep := range f.Replace {
            fmt.Printf("  %s => %s\n", rep.Old.Path, rep.New.Path)
        }
    }
}

Programmatically Creating a go.mod File

package main

import (
    "fmt"
    "log"

    "golang.org/x/mod/modfile"
    "golang.org/x/mod/module"
)

func main() {
    f := &modfile.File{}

    // Set module path
    if err := f.AddModuleStmt("example.com/myproject"); err != nil {
        log.Fatal(err)
    }

    // Set Go version
    if err := f.AddGoStmt("1.21"); err != nil {
        log.Fatal(err)
    }

    // Add requirements
    f.AddNewRequire("golang.org/x/text", "v0.14.0", false)
    f.AddNewRequire("golang.org/x/tools", "v0.16.0", true) // indirect

    // Add a replacement
    if err := f.AddReplace("old.com/pkg", "", "./local/pkg", ""); err != nil {
        log.Fatal(err)
    }

    // Format and write
    data, err := f.Format()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(data))
}

Modifying an Existing go.mod File

package main

import (
    "log"
    "os"

    "golang.org/x/mod/modfile"
)

func main() {
    // Read existing file
    data, err := os.ReadFile("go.mod")
    if err != nil {
        log.Fatal(err)
    }

    f, err := modfile.Parse("go.mod", data, nil)
    if err != nil {
        log.Fatal(err)
    }

    // Update Go version
    if err := f.AddGoStmt("1.21"); err != nil {
        log.Fatal(err)
    }

    // Add a new requirement
    if err := f.AddRequire("github.com/new/package", "v1.0.0"); err != nil {
        log.Fatal(err)
    }

    // Remove old requirement
    if err := f.DropRequire("github.com/old/package"); err != nil {
        log.Fatal(err)
    }

    // Clean up and format
    f.Cleanup()
    f.SortBlocks()

    data, err = f.Format()
    if err != nil {
        log.Fatal(err)
    }

    // Write back
    if err := os.WriteFile("go.mod", data, 0644); err != nil {
        log.Fatal(err)
    }
}

Working with go.work Files

package main

import (
    "fmt"
    "log"
    "os"

    "golang.org/x/mod/modfile"
)

func main() {
    data, err := os.ReadFile("go.work")
    if err != nil {
        log.Fatal(err)
    }

    wf, err := modfile.ParseWork("go.work", data, nil)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Workspace modules:")
    for _, use := range wf.Use {
        fmt.Printf("  %s", use.Path)
        if use.ModulePath != "" {
            fmt.Printf(" (%s)", use.ModulePath)
        }
        fmt.Println()
    }

    // Add a new module to workspace
    wf.AddNewUse("./newmodule", "example.com/newmodule")

    // Format and save
    wf.Cleanup()
    wf.SortBlocks()

    formatted := modfile.Format(wf.Syntax)
    if err := os.WriteFile("go.work", formatted, 0644); err != nil {
        log.Fatal(err)
    }
}

Extracting Module Path from Raw Data

package main

import (
    "fmt"
    "os"

    "golang.org/x/mod/modfile"
)

func main() {
    data, err := os.ReadFile("go.mod")
    if err != nil {
        panic(err)
    }

    // Quick extraction without full parsing
    path := modfile.ModulePath(data)
    fmt.Printf("Module path: %s\n", path)
}

Handling Version Canonicalization

package main

import (
    "fmt"
    "log"
    "os"

    "golang.org/x/mod/modfile"
    "golang.org/x/mod/module"
)

func main() {
    data, err := os.ReadFile("go.mod")
    if err != nil {
        log.Fatal(err)
    }

    // Define a version fixer function
    fix := func(path, version string) (string, error) {
        canonical := module.CanonicalVersion(version)
        if canonical == "" {
            return version, fmt.Errorf("invalid version %q", version)
        }
        return canonical, nil
    }

    f, err := modfile.Parse("go.mod", data, fix)
    if err != nil {
        log.Fatal(err)
    }

    // All versions are now canonicalized
    for _, req := range f.Require {
        fmt.Printf("%s %s\n", req.Mod.Path, req.Mod.Version)
    }
}

See Also