Copyright 2018 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
Package modfile implements a parser and formatter for go.mod files. The go.mod syntax is described in https://golang.org/cmd/go/#hdr-The_go_mod_file. The Parse and ParseLax functions both parse a go.mod file and return an abstract syntax tree. ParseLax ignores unknown statements and may be used to parse go.mod files that may have been developed with newer versions of Go. The File struct returned by Parse and ParseLax represent an abstract go.mod file. File has several methods like AddNewRequire and DropReplace that can be used to programmatically edit a file. The Format function formats a File back to a byte slice which can be written to a file.
package modfile

import (
	
	
	
	
	
	
	

	
	
	
)
A File is the parsed, interpreted form of a go.mod file.
A Module is the module statement.
type Module struct {
	Mod    module.Version
	Syntax *Line
}
A Go is the go statement.
type Go struct {
	Version string // "1.23"
	Syntax  *Line
}
A Require is a single require statement.
type Require struct {
	Mod      module.Version
	Indirect bool // has "// indirect" comment
	Syntax   *Line
}
An Exclude is a single exclude statement.
type Exclude struct {
	Mod    module.Version
	Syntax *Line
}
A Replace is a single replace statement.
A Retract is a single retract statement.
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.
type VersionInterval struct {
	Low, High string
}

func ( *File) ( string) error {
	if .Syntax == nil {
		.Syntax = new(FileSyntax)
	}
	if .Module == nil {
		.Module = &Module{
			Mod:    module.Version{Path: },
			Syntax: .Syntax.addLine(nil, "module", AutoQuote()),
		}
	} else {
		.Module.Mod.Path = 
		.Syntax.updateLine(.Module.Syntax, "module", AutoQuote())
	}
	return nil
}

func ( *File) ( string) {
	if .Syntax == nil {
		.Syntax = new(FileSyntax)
	}
	.Syntax.Stmt = append(.Syntax.Stmt, &CommentBlock{
		Comments: Comments{
			Before: []Comment{
				{
					Token: ,
				},
			},
		},
	})
}

type VersionFixer func(path, version string) (string, error)
Parse parses the data, reported in errors as being from file, into a File struct. It applies fix, if non-nil, to canonicalize all module versions found.
func ( string,  []byte,  VersionFixer) (*File, error) {
	return parseToFile(, , , true)
}
ParseLax is 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 we add in the future will only apply in the main module, like exclude and replace, and so we get better gradual deployments if old go commands simply ignore those statements when found in go.mod files in dependencies.
func ( string,  []byte,  VersionFixer) (*File, error) {
	return parseToFile(, , , false)
}

func ( string,  []byte,  VersionFixer,  bool) (*File, error) {
	,  := parse(, )
	if  != nil {
		return nil, 
	}
	 := &File{
		Syntax: ,
	}

	var  ErrorList
	for ,  := range .Stmt {
		switch x := .(type) {
		case *Line:
			.add(&, nil, , .Token[0], .Token[1:], , )

		case *LineBlock:
			if len(.Token) > 1 {
				if  {
					 = append(, Error{
						Filename: ,
						Pos:      .Start,
						Err:      fmt.Errorf("unknown block type: %s", strings.Join(.Token, " ")),
					})
				}
				continue
			}
			switch .Token[0] {
			default:
				if  {
					 = append(, Error{
						Filename: ,
						Pos:      .Start,
						Err:      fmt.Errorf("unknown block type: %s", strings.Join(.Token, " ")),
					})
				}
				continue
			case "module", "require", "exclude", "replace", "retract":
				for ,  := range .Line {
					.add(&, , , .Token[0], .Token, , )
				}
			}
		}
	}

	if len() > 0 {
		return nil, 
	}
	return , nil
}

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

If strict is false, this module is a dependency. We ignore all unknown directives as well as main-module-only directives like replace and exclude. It will work better for forward compatibility if we can depend on modules that have unknown statements (presumed relevant only when acting as the main module) and simply ignore those statements.
	if ! {
		switch  {
want these even for dependency go.mods
		default:
			return
		}
	}

	 := func( string,  error) {
		* = append(*, Error{
			Filename: .Syntax.Name,
			Pos:      .Start,
			ModPath:  ,
			Verb:     ,
			Err:      ,
		})
	}
	 := func( error) {
		* = append(*, Error{
			Filename: .Syntax.Name,
			Pos:      .Start,
			Err:      ,
		})
	}
	 := func( string,  ...interface{}) {
		(fmt.Errorf(, ...))
	}

	switch  {
	default:
		("unknown directive: %s", )

	case "go":
		if .Go != nil {
			("repeated go statement")
			return
		}
		if len() != 1 {
			("go directive expects exactly one argument")
			return
		} else if !GoVersionRE.MatchString([0]) {
			("invalid go version '%s': must match format 1.23", [0])
			return
		}

		.Go = &Go{Syntax: }
		.Go.Version = [0]

	case "module":
		if .Module != nil {
			("repeated module statement")
			return
		}
		.Module = &Module{Syntax: }
		if len() != 1 {
			("usage: module module/path")
			return
		}
		,  := parseString(&[0])
		if  != nil {
			("invalid quoted string: %v", )
			return
		}
		.Module.Mod = module.Version{Path: }

	case "require", "exclude":
		if len() != 2 {
			("usage: %s module/path v1.2.3", )
			return
		}
		,  := parseString(&[0])
		if  != nil {
			("invalid quoted string: %v", )
			return
		}
		,  := parseVersion(, , &[1], )
		if  != nil {
			()
			return
		}
		,  := modulePathMajor()
		if  != nil {
			()
			return
		}
		if  := module.CheckPathMajor(, );  != nil {
			(, )
			return
		}
		if  == "require" {
			.Require = append(.Require, &Require{
				Mod:      module.Version{Path: , Version: },
				Syntax:   ,
				Indirect: isIndirect(),
			})
		} else {
			.Exclude = append(.Exclude, &Exclude{
				Mod:    module.Version{Path: , Version: },
				Syntax: ,
			})
		}

	case "replace":
		 := 2
		if len() >= 2 && [1] == "=>" {
			 = 1
		}
		if len() < +2 || len() > +3 || [] != "=>" {
			("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", , )
			return
		}
		,  := parseString(&[0])
		if  != nil {
			("invalid quoted string: %v", )
			return
		}
		,  := modulePathMajor()
		if  != nil {
			(, )
			return
		}
		var  string
		if  == 2 {
			,  = parseVersion(, , &[1], )
			if  != nil {
				()
				return
			}
			if  := module.CheckPathMajor(, );  != nil {
				(, )
				return
			}
		}
		,  := parseString(&[+1])
		if  != nil {
			("invalid quoted string: %v", )
			return
		}
		 := ""
		if len() == +2 {
			if !IsDirectoryPath() {
				("replacement module without version must be directory path (rooted or starting with ./ or ../)")
				return
			}
			if filepath.Separator == '/' && strings.Contains(, `\`) {
				("replacement directory appears to be Windows path (on a non-windows system)")
				return
			}
		}
		if len() == +3 {
			,  = parseVersion(, , &[+2], )
			if  != nil {
				()
				return
			}
			if IsDirectoryPath() {
				("replacement module directory path %q cannot have version", )
				return
			}
		}
		.Replace = append(.Replace, &Replace{
			Old:    module.Version{Path: , Version: },
			New:    module.Version{Path: , Version: },
			Syntax: ,
		})

	case "retract":
		 := parseRetractRationale(, )
		,  := parseVersionInterval(, &, )
		if  != nil {
			if  {
				()
				return
Only report errors parsing intervals in the main module. We may support additional syntax in the future, such as open and half-open intervals. Those can't be supported now, because they break the go.mod parser, even in lax mode.
				return
			}
		}
In the future, there may be additional information after the version.
			("unexpected token after version: %q", [0])
			return
		}
		 := &Retract{
			VersionInterval: ,
			Rationale:       ,
			Syntax:          ,
		}
		.Retract = append(.Retract, )
	}
}
isIndirect reports whether line has a "// indirect" comment, meaning it is in go.mod only for its effect on indirect dependencies, so that it can be dropped entirely once the effective version of the indirect dependency reaches the given minimum version.
func ( *Line) bool {
	if len(.Suffix) == 0 {
		return false
	}
	 := strings.Fields(strings.TrimPrefix(.Suffix[0].Token, string(slashSlash)))
	return (len() == 1 && [0] == "indirect" || len() > 1 && [0] == "indirect;")
}
setIndirect sets line to have (or not have) a "// indirect" comment.
func ( *Line,  bool) {
	if isIndirect() ==  {
		return
	}
Adding comment.
New comment.
			.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
			return
		}

		 := &.Suffix[0]
		 := strings.TrimSpace(strings.TrimPrefix(.Token, string(slashSlash)))
Empty comment.
			.Token = "// indirect"
			return
		}
Insert at beginning of existing comment.
		.Token = "// indirect; " + 
		return
	}
Removing comment.
	 := strings.Fields(.Suffix[0].Token)
Remove whole comment.
		.Suffix = nil
		return
	}
Remove comment prefix.
	 := &.Suffix[0]
	 := strings.Index(.Token, "indirect;")
	.Token = "//" + .Token[+len("indirect;"):]
}
IsDirectoryPath reports whether the given path should be interpreted as a directory path. Just like on the go command line, relative paths and rooted paths are directory paths; the rest are module paths.
Because go.mod files can move from one system to another, we check all known path syntaxes, both Unix and Windows.
	return strings.HasPrefix(, "./") || strings.HasPrefix(, "../") || strings.HasPrefix(, "/") ||
		strings.HasPrefix(, `.\`) || strings.HasPrefix(, `..\`) || strings.HasPrefix(, `\`) ||
		len() >= 2 && ('A' <= [0] && [0] <= 'Z' || 'a' <= [0] && [0] <= 'z') && [1] == ':'
}
MustQuote reports whether s must be quoted in order to appear as a single token in a go.mod line.
func ( string) bool {
	for ,  := range  {
		switch  {
		case ' ', '"', '\'', '`':
			return true

		case '(', ')', '[', ']', '{', '}', ',':
			if len() > 1 {
				return true
			}

		default:
			if !unicode.IsPrint() {
				return true
			}
		}
	}
	return  == "" || strings.Contains(, "//") || strings.Contains(, "/*")
}
AutoQuote returns s or, if quoting is required for s to appear in a go.mod, the quotation of s.
func ( string) string {
	if MustQuote() {
		return strconv.Quote()
	}
	return 
}

func ( string,  *[]string,  VersionFixer) (VersionInterval, error) {
	 := *
	if len() == 0 || [0] == "(" {
		return VersionInterval{}, fmt.Errorf("expected '[' or version")
	}
	if [0] != "[" {
		,  := parseVersion(, "", &[0], )
		if  != nil {
			return VersionInterval{}, 
		}
		* = [1:]
		return VersionInterval{Low: , High: }, nil
	}
	 = [1:]

	if len() == 0 {
		return VersionInterval{}, fmt.Errorf("expected version after '['")
	}
	,  := parseVersion(, "", &[0], )
	if  != nil {
		return VersionInterval{}, 
	}
	 = [1:]

	if len() == 0 || [0] != "," {
		return VersionInterval{}, fmt.Errorf("expected ',' after version")
	}
	 = [1:]

	if len() == 0 {
		return VersionInterval{}, fmt.Errorf("expected version after ','")
	}
	,  := parseVersion(, "", &[0], )
	if  != nil {
		return VersionInterval{}, 
	}
	 = [1:]

	if len() == 0 || [0] != "]" {
		return VersionInterval{}, fmt.Errorf("expected ']' after version")
	}
	 = [1:]

	* = 
	return VersionInterval{Low: , High: }, nil
}

func ( *string) (string, error) {
	 := *
	if strings.HasPrefix(, `"`) {
		var  error
		if ,  = strconv.Unquote();  != nil {
			return "", 
		}
Other quotes are reserved both for possible future expansion and to avoid confusion. For example if someone types 'x' we want that to be a syntax error and not a literal x in literal quotation marks.
		return "", fmt.Errorf("unquoted string cannot contain quote")
	}
	* = AutoQuote()
	return , nil
}
parseRetractRationale extracts the rationale for a retract directive from the surrounding comments. If the line does not have comments and is part of a block that does have comments, the block's comments are used.
func ( *LineBlock,  *Line) string {
	 := .Comment()
	if  != nil && len(.Before) == 0 && len(.Suffix) == 0 {
		 = .Comment()
	}
	 := [][]Comment{.Before, .Suffix}
	var  []string
	for ,  := range  {
		for ,  := range  {
			if !strings.HasPrefix(.Token, "//") {
				continue // blank line
			}
			 = append(, strings.TrimSpace(strings.TrimPrefix(.Token, "//")))
		}
	}
	return strings.Join(, "\n")
}

type ErrorList []Error

func ( ErrorList) () string {
	 := make([]string, len())
	for ,  := range  {
		[] = .Error()
	}
	return strings.Join(, "\n")
}

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

func ( *Error) () string {
	var  string
Don't print LineRune if it's 1 (beginning of line). It's always 1 except in scanner errors, which are rare.
		 = fmt.Sprintf("%s:%d:%d: ", .Filename, .Pos.Line, .Pos.LineRune)
	} else if .Pos.Line > 0 {
		 = fmt.Sprintf("%s:%d: ", .Filename, .Pos.Line)
	} else if .Filename != "" {
		 = fmt.Sprintf("%s: ", .Filename)
	}

	var  string
	if .ModPath != "" {
		 = fmt.Sprintf("%s %s: ", .Verb, .ModPath)
	} else if .Verb != "" {
		 = fmt.Sprintf("%s: ", .Verb)
	}

	return  +  + .Err.Error()
}

func ( *Error) () error { return .Err }

func ( string,  string,  *string,  VersionFixer) (string, error) {
	,  := parseString()
	if  != nil {
		return "", &Error{
			Verb:    ,
			ModPath: ,
			Err: &module.InvalidVersionError{
				Version: *,
				Err:     ,
			},
		}
	}
	if  != nil {
		var  error
		,  = (, )
		if  != nil {
			if ,  := .(*module.ModuleError);  {
				return "", &Error{
					Verb:    ,
					ModPath: ,
					Err:     .Err,
				}
			}
			return "", 
		}
	}
	if  := module.CanonicalVersion();  != "" {
		* = 
		return *, nil
	}
	return "", &Error{
		Verb:    ,
		ModPath: ,
		Err: &module.InvalidVersionError{
			Version: ,
			Err:     errors.New("must be of the form v1.2.3"),
		},
	}
}

func ( string) (string, error) {
	, ,  := module.SplitPathVersion()
	if ! {
		return "", fmt.Errorf("invalid module path")
	}
	return , nil
}

func ( *File) () ([]byte, error) {
	return Format(.Syntax), nil
}
Cleanup cleans up the file f after any edit operations. To avoid quadratic behavior, modifications like DropRequire clear the entry but do not remove it from the slice. Cleanup cleans out all the cleared entries.
func ( *File) () {
	 := 0
	for ,  := range .Require {
		if .Mod.Path != "" {
			.Require[] = 
			++
		}
	}
	.Require = .Require[:]

	 = 0
	for ,  := range .Exclude {
		if .Mod.Path != "" {
			.Exclude[] = 
			++
		}
	}
	.Exclude = .Exclude[:]

	 = 0
	for ,  := range .Replace {
		if .Old.Path != "" {
			.Replace[] = 
			++
		}
	}
	.Replace = .Replace[:]

	 = 0
	for ,  := range .Retract {
		if .Low != "" || .High != "" {
			.Retract[] = 
			++
		}
	}
	.Retract = .Retract[:]

	.Syntax.Cleanup()
}

func ( *File) ( string) error {
	if !GoVersionRE.MatchString() {
		return fmt.Errorf("invalid language version string %q", )
	}
	if .Go == nil {
		var  Expr
		if .Module != nil && .Module.Syntax != nil {
			 = .Module.Syntax
		}
		.Go = &Go{
			Version: ,
			Syntax:  .Syntax.addLine(, "go", ),
		}
	} else {
		.Go.Version = 
		.Syntax.updateLine(.Go.Syntax, "go", )
	}
	return nil
}

func ( *File) (,  string) error {
	 := true
	for ,  := range .Require {
		if .Mod.Path ==  {
			if  {
				.Mod.Version = 
				.Syntax.updateLine(.Syntax, "require", AutoQuote(), )
				 = false
			} else {
				.Syntax.removeLine(.Syntax)
				* = Require{}
			}
		}
	}

	if  {
		.AddNewRequire(, , false)
	}
	return nil
}

func ( *File) (,  string,  bool) {
	 := .Syntax.addLine(nil, "require", AutoQuote(), )
	setIndirect(, )
	.Require = append(.Require, &Require{module.Version{Path: , Version: }, , })
}

func ( *File) ( []*Require) {
	 := make(map[string]string)
	 := make(map[string]bool)
	for ,  := range  {
		[.Mod.Path] = .Mod.Version
		[.Mod.Path] = .Indirect
	}

	for ,  := range .Require {
		if ,  := [.Mod.Path];  {
			.Mod.Version = 
			.Indirect = [.Mod.Path]
		} else {
			* = Require{}
		}
	}

	var  []Expr
	for ,  := range .Syntax.Stmt {
		switch stmt := .(type) {
		case *LineBlock:
			if len(.Token) > 0 && .Token[0] == "require" {
				var  []*Line
				for ,  := range .Line {
					if ,  := parseString(&.Token[0]);  == nil && [] != "" {
						if len(.Comments.Before) == 1 && len(.Comments.Before[0].Token) == 0 {
							.Comments.Before = .Comments.Before[:0]
						}
						.Token[1] = []
						delete(, )
						setIndirect(, [])
						 = append(, )
					}
				}
				if len() == 0 {
					continue // drop stmt
				}
				.Line = 
			}

		case *Line:
			if len(.Token) > 0 && .Token[0] == "require" {
				if ,  := parseString(&.Token[1]);  == nil && [] != "" {
					.Token[2] = []
					delete(, )
					setIndirect(, [])
				} else {
					continue // drop stmt
				}
			}
		}
		 = append(, )
	}
	.Syntax.Stmt = 

	for ,  := range  {
		.AddNewRequire(, , [])
	}
	.SortBlocks()
}

func ( *File) ( string) error {
	for ,  := range .Require {
		if .Mod.Path ==  {
			.Syntax.removeLine(.Syntax)
			* = Require{}
		}
	}
	return nil
}

func ( *File) (,  string) error {
	var  *Line
	for ,  := range .Exclude {
		if .Mod.Path ==  && .Mod.Version ==  {
			return nil
		}
		if .Mod.Path ==  {
			 = .Syntax
		}
	}

	.Exclude = append(.Exclude, &Exclude{Mod: module.Version{Path: , Version: }, Syntax: .Syntax.addLine(, "exclude", AutoQuote(), )})
	return nil
}

func ( *File) (,  string) error {
	for ,  := range .Exclude {
		if .Mod.Path ==  && .Mod.Version ==  {
			.Syntax.removeLine(.Syntax)
			* = Exclude{}
		}
	}
	return nil
}

func ( *File) (, , ,  string) error {
	 := true
	 := module.Version{Path: , Version: }
	 := module.Version{Path: , Version: }
	 := []string{"replace", AutoQuote()}
	if  != "" {
		 = append(, )
	}
	 = append(, "=>", AutoQuote())
	if  != "" {
		 = append(, )
	}

	var  *Line
	for ,  := range .Replace {
		if .Old.Path ==  && ( == "" || .Old.Version == ) {
Found replacement for old; update to use new.
				.New = 
				.Syntax.updateLine(.Syntax, ...)
				 = false
				continue
Already added; delete other replacements for same.
			.Syntax.removeLine(.Syntax)
			* = Replace{}
		}
		if .Old.Path ==  {
			 = .Syntax
		}
	}
	if  {
		.Replace = append(.Replace, &Replace{Old: , New: , Syntax: .Syntax.addLine(, ...)})
	}
	return nil
}

func ( *File) (,  string) error {
	for ,  := range .Replace {
		if .Old.Path ==  && .Old.Version ==  {
			.Syntax.removeLine(.Syntax)
			* = Replace{}
		}
	}
	return nil
}

func ( *File) ( VersionInterval,  string) error {
	 := &Retract{
		VersionInterval: ,
	}
	if .Low == .High {
		.Syntax = .Syntax.addLine(nil, "retract", AutoQuote(.Low))
	} else {
		.Syntax = .Syntax.addLine(nil, "retract", "[", AutoQuote(.Low), ",", AutoQuote(.High), "]")
	}
	if  != "" {
		for ,  := range strings.Split(, "\n") {
			 := Comment{Token: "// " + }
			.Syntax.Comment().Before = append(.Syntax.Comment().Before, )
		}
	}
	return nil
}

func ( *File) ( VersionInterval) error {
	for ,  := range .Retract {
		if .VersionInterval ==  {
			.Syntax.removeLine(.Syntax)
			* = Retract{}
		}
	}
	return nil
}

func ( *File) () {
	.removeDups() // otherwise sorting is unsafe

	for ,  := range .Syntax.Stmt {
		,  := .(*LineBlock)
		if ! {
			continue
		}
		 := lineLess
		if .Token[0] == "retract" {
			 = lineRetractLess
		}
		sort.SliceStable(.Line, func(,  int) bool {
			return (.Line[], .Line[])
		})
	}
}
removeDups removes duplicate exclude and replace directives. Earlier exclude directives take priority. Later replace directives take priority. require directives are not de-duplicated. That's left up to higher-level logic (MVS). retract directives are not de-duplicated since comments are meaningful, and versions may be retracted multiple times.
func ( *File) () {
	 := make(map[*Line]bool)
Remove duplicate excludes.
	 := make(map[module.Version]bool)
	for ,  := range .Exclude {
		if [.Mod] {
			[.Syntax] = true
			continue
		}
		[.Mod] = true
	}
	var  []*Exclude
	for ,  := range .Exclude {
		if ![.Syntax] {
			 = append(, )
		}
	}
	.Exclude = 
Remove duplicate replacements. Later replacements take priority over earlier ones.
	 := make(map[module.Version]bool)
	for  := len(.Replace) - 1;  >= 0; -- {
		 := .Replace[]
		if [.Old] {
			[.Syntax] = true
			continue
		}
		[.Old] = true
	}
	var  []*Replace
	for ,  := range .Replace {
		if ![.Syntax] {
			 = append(, )
		}
	}
	.Replace = 
Duplicate require and retract directives are not removed.
Drop killed statements from the syntax tree.
	var  []Expr
	for ,  := range .Syntax.Stmt {
		switch stmt := .(type) {
		case *Line:
			if [] {
				continue
			}
		case *LineBlock:
			var  []*Line
			for ,  := range .Line {
				if ![] {
					 = append(, )
				}
			}
			.Line = 
			if len() == 0 {
				continue
			}
		}
		 = append(, )
	}
	.Syntax.Stmt = 
}
lineLess returns whether li should be sorted before lj. It sorts lexicographically without assigning any special meaning to tokens.
func (,  *Line) bool {
	for  := 0;  < len(.Token) &&  < len(.Token); ++ {
		if .Token[] != .Token[] {
			return .Token[] < .Token[]
		}
	}
	return len(.Token) < len(.Token)
}
lineRetractLess returns whether li should be sorted before lj for lines in a "retract" block. It treats each line as a version interval. Single versions are compared as if they were intervals with the same low and high version. Intervals are sorted in descending order, first by low version, then by high version, using semver.Compare.
func (,  *Line) bool {
	 := func( *Line) VersionInterval {
		if len(.Token) == 1 {
			return VersionInterval{Low: .Token[0], High: .Token[0]}
		} else if len(.Token) == 5 && .Token[0] == "[" && .Token[2] == "," && .Token[4] == "]" {
			return VersionInterval{Low: .Token[1], High: .Token[3]}
Line in unknown format. Treat as an invalid version.
			return VersionInterval{}
		}
	}
	 := ()
	 := ()
	if  := semver.Compare(.Low, .Low);  != 0 {
		return  > 0
	}
	return semver.Compare(.High, .High) > 0