| // 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://pkg.go.dev/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 [File.AddNewRequire] and | 
 | // [File.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 ( | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"path/filepath" | 
 | 	"sort" | 
 | 	"strconv" | 
 | 	"strings" | 
 | 	"unicode" | 
 |  | 
 | 	"golang.org/x/mod/internal/lazyregexp" | 
 | 	"golang.org/x/mod/module" | 
 | 	"golang.org/x/mod/semver" | 
 | ) | 
 |  | 
 | // A File is the parsed, interpreted form of a go.mod file. | 
 | type File struct { | 
 | 	Module    *Module | 
 | 	Go        *Go | 
 | 	Toolchain *Toolchain | 
 | 	Require   []*Require | 
 | 	Exclude   []*Exclude | 
 | 	Replace   []*Replace | 
 | 	Retract   []*Retract | 
 |  | 
 | 	Syntax *FileSyntax | 
 | } | 
 |  | 
 | // A Module is the module statement. | 
 | type Module struct { | 
 | 	Mod        module.Version | 
 | 	Deprecated string | 
 | 	Syntax     *Line | 
 | } | 
 |  | 
 | // A Go is the go statement. | 
 | type Go struct { | 
 | 	Version string // "1.23" | 
 | 	Syntax  *Line | 
 | } | 
 |  | 
 | // A Toolchain is the toolchain statement. | 
 | type Toolchain struct { | 
 | 	Name   string // "go1.21rc1" | 
 | 	Syntax *Line | 
 | } | 
 |  | 
 | // An Exclude is a single exclude statement. | 
 | type Exclude struct { | 
 | 	Mod    module.Version | 
 | 	Syntax *Line | 
 | } | 
 |  | 
 | // A Replace is a single replace statement. | 
 | type Replace struct { | 
 | 	Old    module.Version | 
 | 	New    module.Version | 
 | 	Syntax *Line | 
 | } | 
 |  | 
 | // A Retract is a single retract statement. | 
 | type Retract struct { | 
 | 	VersionInterval | 
 | 	Rationale string | 
 | 	Syntax    *Line | 
 | } | 
 |  | 
 | // 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 | 
 | } | 
 |  | 
 | // A Require is a single require statement. | 
 | type Require struct { | 
 | 	Mod      module.Version | 
 | 	Indirect bool // has "// indirect" comment | 
 | 	Syntax   *Line | 
 | } | 
 |  | 
 | func (r *Require) markRemoved() { | 
 | 	r.Syntax.markRemoved() | 
 | 	*r = Require{} | 
 | } | 
 |  | 
 | func (r *Require) setVersion(v string) { | 
 | 	r.Mod.Version = v | 
 |  | 
 | 	if line := r.Syntax; len(line.Token) > 0 { | 
 | 		if line.InBlock { | 
 | 			// If the line is preceded by an empty line, remove it; see | 
 | 			// https://golang.org/issue/33779. | 
 | 			if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 { | 
 | 				line.Comments.Before = line.Comments.Before[:0] | 
 | 			} | 
 | 			if len(line.Token) >= 2 { // example.com v1.2.3 | 
 | 				line.Token[1] = v | 
 | 			} | 
 | 		} else { | 
 | 			if len(line.Token) >= 3 { // require example.com v1.2.3 | 
 | 				line.Token[2] = v | 
 | 			} | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | // setIndirect sets line to have (or not have) a "// indirect" comment. | 
 | func (r *Require) setIndirect(indirect bool) { | 
 | 	r.Indirect = indirect | 
 | 	line := r.Syntax | 
 | 	if isIndirect(line) == indirect { | 
 | 		return | 
 | 	} | 
 | 	if indirect { | 
 | 		// Adding comment. | 
 | 		if len(line.Suffix) == 0 { | 
 | 			// New comment. | 
 | 			line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} | 
 | 			return | 
 | 		} | 
 |  | 
 | 		com := &line.Suffix[0] | 
 | 		text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash))) | 
 | 		if text == "" { | 
 | 			// Empty comment. | 
 | 			com.Token = "// indirect" | 
 | 			return | 
 | 		} | 
 |  | 
 | 		// Insert at beginning of existing comment. | 
 | 		com.Token = "// indirect; " + text | 
 | 		return | 
 | 	} | 
 |  | 
 | 	// Removing comment. | 
 | 	f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) | 
 | 	if f == "indirect" { | 
 | 		// Remove whole comment. | 
 | 		line.Suffix = nil | 
 | 		return | 
 | 	} | 
 |  | 
 | 	// Remove comment prefix. | 
 | 	com := &line.Suffix[0] | 
 | 	i := strings.Index(com.Token, "indirect;") | 
 | 	com.Token = "//" + com.Token[i+len("indirect;"):] | 
 | } | 
 |  | 
 | // 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 isIndirect(line *Line) bool { | 
 | 	if len(line.Suffix) == 0 { | 
 | 		return false | 
 | 	} | 
 | 	f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) | 
 | 	return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;") | 
 | } | 
 |  | 
 | func (f *File) AddModuleStmt(path string) error { | 
 | 	if f.Syntax == nil { | 
 | 		f.Syntax = new(FileSyntax) | 
 | 	} | 
 | 	if f.Module == nil { | 
 | 		f.Module = &Module{ | 
 | 			Mod:    module.Version{Path: path}, | 
 | 			Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), | 
 | 		} | 
 | 	} else { | 
 | 		f.Module.Mod.Path = path | 
 | 		f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func (f *File) AddComment(text string) { | 
 | 	if f.Syntax == nil { | 
 | 		f.Syntax = new(FileSyntax) | 
 | 	} | 
 | 	f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ | 
 | 		Comments: Comments{ | 
 | 			Before: []Comment{ | 
 | 				{ | 
 | 					Token: text, | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 | } | 
 |  | 
 | type VersionFixer func(path, version string) (string, error) | 
 |  | 
 | // errDontFix is returned by a VersionFixer to indicate the version should be | 
 | // left alone, even if it's not canonical. | 
 | var dontFixRetract VersionFixer = func(_, vers string) (string, error) { | 
 | 	return vers, nil | 
 | } | 
 |  | 
 | // Parse parses and returns a go.mod file. | 
 | // | 
 | // file is the name of the file, used in positions and errors. | 
 | // | 
 | // data is the content of the file. | 
 | // | 
 | // fix is an optional function that canonicalizes module versions. | 
 | // If fix is nil, all module versions must be canonical ([module.CanonicalVersion] | 
 | // must return the same string). | 
 | func Parse(file string, data []byte, fix VersionFixer) (*File, error) { | 
 | 	return parseToFile(file, data, fix, 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 ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { | 
 | 	return parseToFile(file, data, fix, false) | 
 | } | 
 |  | 
 | func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) { | 
 | 	fs, err := parse(file, data) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	f := &File{ | 
 | 		Syntax: fs, | 
 | 	} | 
 | 	var errs ErrorList | 
 |  | 
 | 	// fix versions in retract directives after the file is parsed. | 
 | 	// We need the module path to fix versions, and it might be at the end. | 
 | 	defer func() { | 
 | 		oldLen := len(errs) | 
 | 		f.fixRetract(fix, &errs) | 
 | 		if len(errs) > oldLen { | 
 | 			parsed, err = nil, errs | 
 | 		} | 
 | 	}() | 
 |  | 
 | 	for _, x := range fs.Stmt { | 
 | 		switch x := x.(type) { | 
 | 		case *Line: | 
 | 			f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict) | 
 |  | 
 | 		case *LineBlock: | 
 | 			if len(x.Token) > 1 { | 
 | 				if strict { | 
 | 					errs = append(errs, Error{ | 
 | 						Filename: file, | 
 | 						Pos:      x.Start, | 
 | 						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), | 
 | 					}) | 
 | 				} | 
 | 				continue | 
 | 			} | 
 | 			switch x.Token[0] { | 
 | 			default: | 
 | 				if strict { | 
 | 					errs = append(errs, Error{ | 
 | 						Filename: file, | 
 | 						Pos:      x.Start, | 
 | 						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), | 
 | 					}) | 
 | 				} | 
 | 				continue | 
 | 			case "module", "require", "exclude", "replace", "retract": | 
 | 				for _, l := range x.Line { | 
 | 					f.add(&errs, x, l, x.Token[0], l.Token, fix, strict) | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if len(errs) > 0 { | 
 | 		return nil, errs | 
 | 	} | 
 | 	return f, nil | 
 | } | 
 |  | 
 | var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`) | 
 | var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`) | 
 |  | 
 | // Toolchains must be named beginning with `go1`, | 
 | // like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. | 
 | // TODO(samthanawalla): Replace regex with https://pkg.go.dev/go/version#IsValid in 1.23+ | 
 | var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`) | 
 |  | 
 | func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) { | 
 | 	// 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 !strict { | 
 | 		switch verb { | 
 | 		case "go", "module", "retract", "require": | 
 | 			// want these even for dependency go.mods | 
 | 		default: | 
 | 			return | 
 | 		} | 
 | 	} | 
 |  | 
 | 	wrapModPathError := func(modPath string, err error) { | 
 | 		*errs = append(*errs, Error{ | 
 | 			Filename: f.Syntax.Name, | 
 | 			Pos:      line.Start, | 
 | 			ModPath:  modPath, | 
 | 			Verb:     verb, | 
 | 			Err:      err, | 
 | 		}) | 
 | 	} | 
 | 	wrapError := func(err error) { | 
 | 		*errs = append(*errs, Error{ | 
 | 			Filename: f.Syntax.Name, | 
 | 			Pos:      line.Start, | 
 | 			Err:      err, | 
 | 		}) | 
 | 	} | 
 | 	errorf := func(format string, args ...interface{}) { | 
 | 		wrapError(fmt.Errorf(format, args...)) | 
 | 	} | 
 |  | 
 | 	switch verb { | 
 | 	default: | 
 | 		errorf("unknown directive: %s", verb) | 
 |  | 
 | 	case "go": | 
 | 		if f.Go != nil { | 
 | 			errorf("repeated go statement") | 
 | 			return | 
 | 		} | 
 | 		if len(args) != 1 { | 
 | 			errorf("go directive expects exactly one argument") | 
 | 			return | 
 | 		} else if !GoVersionRE.MatchString(args[0]) { | 
 | 			fixed := false | 
 | 			if !strict { | 
 | 				if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil { | 
 | 					args[0] = m[1] | 
 | 					fixed = true | 
 | 				} | 
 | 			} | 
 | 			if !fixed { | 
 | 				errorf("invalid go version '%s': must match format 1.23.0", args[0]) | 
 | 				return | 
 | 			} | 
 | 		} | 
 |  | 
 | 		f.Go = &Go{Syntax: line} | 
 | 		f.Go.Version = args[0] | 
 |  | 
 | 	case "toolchain": | 
 | 		if f.Toolchain != nil { | 
 | 			errorf("repeated toolchain statement") | 
 | 			return | 
 | 		} | 
 | 		if len(args) != 1 { | 
 | 			errorf("toolchain directive expects exactly one argument") | 
 | 			return | 
 | 		} else if strict && !ToolchainRE.MatchString(args[0]) { | 
 | 			errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) | 
 | 			return | 
 | 		} | 
 | 		f.Toolchain = &Toolchain{Syntax: line} | 
 | 		f.Toolchain.Name = args[0] | 
 |  | 
 | 	case "module": | 
 | 		if f.Module != nil { | 
 | 			errorf("repeated module statement") | 
 | 			return | 
 | 		} | 
 | 		deprecated := parseDeprecation(block, line) | 
 | 		f.Module = &Module{ | 
 | 			Syntax:     line, | 
 | 			Deprecated: deprecated, | 
 | 		} | 
 | 		if len(args) != 1 { | 
 | 			errorf("usage: module module/path") | 
 | 			return | 
 | 		} | 
 | 		s, err := parseString(&args[0]) | 
 | 		if err != nil { | 
 | 			errorf("invalid quoted string: %v", err) | 
 | 			return | 
 | 		} | 
 | 		f.Module.Mod = module.Version{Path: s} | 
 |  | 
 | 	case "require", "exclude": | 
 | 		if len(args) != 2 { | 
 | 			errorf("usage: %s module/path v1.2.3", verb) | 
 | 			return | 
 | 		} | 
 | 		s, err := parseString(&args[0]) | 
 | 		if err != nil { | 
 | 			errorf("invalid quoted string: %v", err) | 
 | 			return | 
 | 		} | 
 | 		v, err := parseVersion(verb, s, &args[1], fix) | 
 | 		if err != nil { | 
 | 			wrapError(err) | 
 | 			return | 
 | 		} | 
 | 		pathMajor, err := modulePathMajor(s) | 
 | 		if err != nil { | 
 | 			wrapError(err) | 
 | 			return | 
 | 		} | 
 | 		if err := module.CheckPathMajor(v, pathMajor); err != nil { | 
 | 			wrapModPathError(s, err) | 
 | 			return | 
 | 		} | 
 | 		if verb == "require" { | 
 | 			f.Require = append(f.Require, &Require{ | 
 | 				Mod:      module.Version{Path: s, Version: v}, | 
 | 				Syntax:   line, | 
 | 				Indirect: isIndirect(line), | 
 | 			}) | 
 | 		} else { | 
 | 			f.Exclude = append(f.Exclude, &Exclude{ | 
 | 				Mod:    module.Version{Path: s, Version: v}, | 
 | 				Syntax: line, | 
 | 			}) | 
 | 		} | 
 |  | 
 | 	case "replace": | 
 | 		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) | 
 | 		if wrappederr != nil { | 
 | 			*errs = append(*errs, *wrappederr) | 
 | 			return | 
 | 		} | 
 | 		f.Replace = append(f.Replace, replace) | 
 |  | 
 | 	case "retract": | 
 | 		rationale := parseDirectiveComment(block, line) | 
 | 		vi, err := parseVersionInterval(verb, "", &args, dontFixRetract) | 
 | 		if err != nil { | 
 | 			if strict { | 
 | 				wrapError(err) | 
 | 				return | 
 | 			} else { | 
 | 				// 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 | 
 | 			} | 
 | 		} | 
 | 		if len(args) > 0 && strict { | 
 | 			// In the future, there may be additional information after the version. | 
 | 			errorf("unexpected token after version: %q", args[0]) | 
 | 			return | 
 | 		} | 
 | 		retract := &Retract{ | 
 | 			VersionInterval: vi, | 
 | 			Rationale:       rationale, | 
 | 			Syntax:          line, | 
 | 		} | 
 | 		f.Retract = append(f.Retract, retract) | 
 | 	} | 
 | } | 
 |  | 
 | func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) { | 
 | 	wrapModPathError := func(modPath string, err error) *Error { | 
 | 		return &Error{ | 
 | 			Filename: filename, | 
 | 			Pos:      line.Start, | 
 | 			ModPath:  modPath, | 
 | 			Verb:     verb, | 
 | 			Err:      err, | 
 | 		} | 
 | 	} | 
 | 	wrapError := func(err error) *Error { | 
 | 		return &Error{ | 
 | 			Filename: filename, | 
 | 			Pos:      line.Start, | 
 | 			Err:      err, | 
 | 		} | 
 | 	} | 
 | 	errorf := func(format string, args ...interface{}) *Error { | 
 | 		return wrapError(fmt.Errorf(format, args...)) | 
 | 	} | 
 |  | 
 | 	arrow := 2 | 
 | 	if len(args) >= 2 && args[1] == "=>" { | 
 | 		arrow = 1 | 
 | 	} | 
 | 	if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { | 
 | 		return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb) | 
 | 	} | 
 | 	s, err := parseString(&args[0]) | 
 | 	if err != nil { | 
 | 		return nil, errorf("invalid quoted string: %v", err) | 
 | 	} | 
 | 	pathMajor, err := modulePathMajor(s) | 
 | 	if err != nil { | 
 | 		return nil, wrapModPathError(s, err) | 
 |  | 
 | 	} | 
 | 	var v string | 
 | 	if arrow == 2 { | 
 | 		v, err = parseVersion(verb, s, &args[1], fix) | 
 | 		if err != nil { | 
 | 			return nil, wrapError(err) | 
 | 		} | 
 | 		if err := module.CheckPathMajor(v, pathMajor); err != nil { | 
 | 			return nil, wrapModPathError(s, err) | 
 | 		} | 
 | 	} | 
 | 	ns, err := parseString(&args[arrow+1]) | 
 | 	if err != nil { | 
 | 		return nil, errorf("invalid quoted string: %v", err) | 
 | 	} | 
 | 	nv := "" | 
 | 	if len(args) == arrow+2 { | 
 | 		if !IsDirectoryPath(ns) { | 
 | 			if strings.Contains(ns, "@") { | 
 | 				return nil, errorf("replacement module must match format 'path version', not 'path@version'") | 
 | 			} | 
 | 			return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)") | 
 | 		} | 
 | 		if filepath.Separator == '/' && strings.Contains(ns, `\`) { | 
 | 			return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)") | 
 | 		} | 
 | 	} | 
 | 	if len(args) == arrow+3 { | 
 | 		nv, err = parseVersion(verb, ns, &args[arrow+2], fix) | 
 | 		if err != nil { | 
 | 			return nil, wrapError(err) | 
 | 		} | 
 | 		if IsDirectoryPath(ns) { | 
 | 			return nil, errorf("replacement module directory path %q cannot have version", ns) | 
 | 		} | 
 | 	} | 
 | 	return &Replace{ | 
 | 		Old:    module.Version{Path: s, Version: v}, | 
 | 		New:    module.Version{Path: ns, Version: nv}, | 
 | 		Syntax: line, | 
 | 	}, nil | 
 | } | 
 |  | 
 | // fixRetract applies fix to each retract directive in f, appending any errors | 
 | // to errs. | 
 | // | 
 | // Most versions are fixed as we parse the file, but for retract directives, | 
 | // the relevant module path is the one specified with the module directive, | 
 | // and that might appear at the end of the file (or not at all). | 
 | func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) { | 
 | 	if fix == nil { | 
 | 		return | 
 | 	} | 
 | 	path := "" | 
 | 	if f.Module != nil { | 
 | 		path = f.Module.Mod.Path | 
 | 	} | 
 | 	var r *Retract | 
 | 	wrapError := func(err error) { | 
 | 		*errs = append(*errs, Error{ | 
 | 			Filename: f.Syntax.Name, | 
 | 			Pos:      r.Syntax.Start, | 
 | 			Err:      err, | 
 | 		}) | 
 | 	} | 
 |  | 
 | 	for _, r = range f.Retract { | 
 | 		if path == "" { | 
 | 			wrapError(errors.New("no module directive found, so retract cannot be used")) | 
 | 			return // only print the first one of these | 
 | 		} | 
 |  | 
 | 		args := r.Syntax.Token | 
 | 		if args[0] == "retract" { | 
 | 			args = args[1:] | 
 | 		} | 
 | 		vi, err := parseVersionInterval("retract", path, &args, fix) | 
 | 		if err != nil { | 
 | 			wrapError(err) | 
 | 		} | 
 | 		r.VersionInterval = vi | 
 | 	} | 
 | } | 
 |  | 
 | func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) { | 
 | 	wrapError := func(err error) { | 
 | 		*errs = append(*errs, Error{ | 
 | 			Filename: f.Syntax.Name, | 
 | 			Pos:      line.Start, | 
 | 			Err:      err, | 
 | 		}) | 
 | 	} | 
 | 	errorf := func(format string, args ...interface{}) { | 
 | 		wrapError(fmt.Errorf(format, args...)) | 
 | 	} | 
 |  | 
 | 	switch verb { | 
 | 	default: | 
 | 		errorf("unknown directive: %s", verb) | 
 |  | 
 | 	case "go": | 
 | 		if f.Go != nil { | 
 | 			errorf("repeated go statement") | 
 | 			return | 
 | 		} | 
 | 		if len(args) != 1 { | 
 | 			errorf("go directive expects exactly one argument") | 
 | 			return | 
 | 		} else if !GoVersionRE.MatchString(args[0]) { | 
 | 			errorf("invalid go version '%s': must match format 1.23.0", args[0]) | 
 | 			return | 
 | 		} | 
 |  | 
 | 		f.Go = &Go{Syntax: line} | 
 | 		f.Go.Version = args[0] | 
 |  | 
 | 	case "toolchain": | 
 | 		if f.Toolchain != nil { | 
 | 			errorf("repeated toolchain statement") | 
 | 			return | 
 | 		} | 
 | 		if len(args) != 1 { | 
 | 			errorf("toolchain directive expects exactly one argument") | 
 | 			return | 
 | 		} else if !ToolchainRE.MatchString(args[0]) { | 
 | 			errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) | 
 | 			return | 
 | 		} | 
 |  | 
 | 		f.Toolchain = &Toolchain{Syntax: line} | 
 | 		f.Toolchain.Name = args[0] | 
 |  | 
 | 	case "use": | 
 | 		if len(args) != 1 { | 
 | 			errorf("usage: %s local/dir", verb) | 
 | 			return | 
 | 		} | 
 | 		s, err := parseString(&args[0]) | 
 | 		if err != nil { | 
 | 			errorf("invalid quoted string: %v", err) | 
 | 			return | 
 | 		} | 
 | 		f.Use = append(f.Use, &Use{ | 
 | 			Path:   s, | 
 | 			Syntax: line, | 
 | 		}) | 
 |  | 
 | 	case "replace": | 
 | 		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) | 
 | 		if wrappederr != nil { | 
 | 			*errs = append(*errs, *wrappederr) | 
 | 			return | 
 | 		} | 
 | 		f.Replace = append(f.Replace, replace) | 
 | 	} | 
 | } | 
 |  | 
 | // IsDirectoryPath 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. | 
 | func IsDirectoryPath(ns string) bool { | 
 | 	// Because go.mod files can move from one system to another, | 
 | 	// we check all known path syntaxes, both Unix and Windows. | 
 | 	return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) || | 
 | 		ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) || | 
 | 		strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) || | 
 | 		len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' | 
 | } | 
 |  | 
 | // MustQuote reports whether s must be quoted in order to appear as | 
 | // a single token in a go.mod line. | 
 | func MustQuote(s string) bool { | 
 | 	for _, r := range s { | 
 | 		switch r { | 
 | 		case ' ', '"', '\'', '`': | 
 | 			return true | 
 |  | 
 | 		case '(', ')', '[', ']', '{', '}', ',': | 
 | 			if len(s) > 1 { | 
 | 				return true | 
 | 			} | 
 |  | 
 | 		default: | 
 | 			if !unicode.IsPrint(r) { | 
 | 				return true | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") | 
 | } | 
 |  | 
 | // AutoQuote returns s or, if quoting is required for s to appear in a go.mod, | 
 | // the quotation of s. | 
 | func AutoQuote(s string) string { | 
 | 	if MustQuote(s) { | 
 | 		return strconv.Quote(s) | 
 | 	} | 
 | 	return s | 
 | } | 
 |  | 
 | func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) { | 
 | 	toks := *args | 
 | 	if len(toks) == 0 || toks[0] == "(" { | 
 | 		return VersionInterval{}, fmt.Errorf("expected '[' or version") | 
 | 	} | 
 | 	if toks[0] != "[" { | 
 | 		v, err := parseVersion(verb, path, &toks[0], fix) | 
 | 		if err != nil { | 
 | 			return VersionInterval{}, err | 
 | 		} | 
 | 		*args = toks[1:] | 
 | 		return VersionInterval{Low: v, High: v}, nil | 
 | 	} | 
 | 	toks = toks[1:] | 
 |  | 
 | 	if len(toks) == 0 { | 
 | 		return VersionInterval{}, fmt.Errorf("expected version after '['") | 
 | 	} | 
 | 	low, err := parseVersion(verb, path, &toks[0], fix) | 
 | 	if err != nil { | 
 | 		return VersionInterval{}, err | 
 | 	} | 
 | 	toks = toks[1:] | 
 |  | 
 | 	if len(toks) == 0 || toks[0] != "," { | 
 | 		return VersionInterval{}, fmt.Errorf("expected ',' after version") | 
 | 	} | 
 | 	toks = toks[1:] | 
 |  | 
 | 	if len(toks) == 0 { | 
 | 		return VersionInterval{}, fmt.Errorf("expected version after ','") | 
 | 	} | 
 | 	high, err := parseVersion(verb, path, &toks[0], fix) | 
 | 	if err != nil { | 
 | 		return VersionInterval{}, err | 
 | 	} | 
 | 	toks = toks[1:] | 
 |  | 
 | 	if len(toks) == 0 || toks[0] != "]" { | 
 | 		return VersionInterval{}, fmt.Errorf("expected ']' after version") | 
 | 	} | 
 | 	toks = toks[1:] | 
 |  | 
 | 	*args = toks | 
 | 	return VersionInterval{Low: low, High: high}, nil | 
 | } | 
 |  | 
 | func parseString(s *string) (string, error) { | 
 | 	t := *s | 
 | 	if strings.HasPrefix(t, `"`) { | 
 | 		var err error | 
 | 		if t, err = strconv.Unquote(t); err != nil { | 
 | 			return "", err | 
 | 		} | 
 | 	} else if strings.ContainsAny(t, "\"'`") { | 
 | 		// 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") | 
 | 	} | 
 | 	*s = AutoQuote(t) | 
 | 	return t, nil | 
 | } | 
 |  | 
 | var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`) | 
 |  | 
 | // parseDeprecation extracts the text of comments on a "module" directive and | 
 | // extracts a deprecation message from that. | 
 | // | 
 | // A deprecation message is contained in a paragraph within a block of comments | 
 | // that starts with "Deprecated:" (case sensitive). The message runs until the | 
 | // end of the paragraph and does not include the "Deprecated:" prefix. If the | 
 | // comment block has multiple paragraphs that start with "Deprecated:", | 
 | // parseDeprecation returns the message from the first. | 
 | func parseDeprecation(block *LineBlock, line *Line) string { | 
 | 	text := parseDirectiveComment(block, line) | 
 | 	m := deprecatedRE.FindStringSubmatch(text) | 
 | 	if m == nil { | 
 | 		return "" | 
 | 	} | 
 | 	return m[1] | 
 | } | 
 |  | 
 | // parseDirectiveComment extracts the text of comments on a directive. | 
 | // If the directive's line does not have comments and is part of a block that | 
 | // does have comments, the block's comments are used. | 
 | func parseDirectiveComment(block *LineBlock, line *Line) string { | 
 | 	comments := line.Comment() | 
 | 	if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 { | 
 | 		comments = block.Comment() | 
 | 	} | 
 | 	groups := [][]Comment{comments.Before, comments.Suffix} | 
 | 	var lines []string | 
 | 	for _, g := range groups { | 
 | 		for _, c := range g { | 
 | 			if !strings.HasPrefix(c.Token, "//") { | 
 | 				continue // blank line | 
 | 			} | 
 | 			lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//"))) | 
 | 		} | 
 | 	} | 
 | 	return strings.Join(lines, "\n") | 
 | } | 
 |  | 
 | type ErrorList []Error | 
 |  | 
 | func (e ErrorList) Error() string { | 
 | 	errStrs := make([]string, len(e)) | 
 | 	for i, err := range e { | 
 | 		errStrs[i] = err.Error() | 
 | 	} | 
 | 	return strings.Join(errStrs, "\n") | 
 | } | 
 |  | 
 | type Error struct { | 
 | 	Filename string | 
 | 	Pos      Position | 
 | 	Verb     string | 
 | 	ModPath  string | 
 | 	Err      error | 
 | } | 
 |  | 
 | func (e *Error) Error() string { | 
 | 	var pos string | 
 | 	if e.Pos.LineRune > 1 { | 
 | 		// Don't print LineRune if it's 1 (beginning of line). | 
 | 		// It's always 1 except in scanner errors, which are rare. | 
 | 		pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune) | 
 | 	} else if e.Pos.Line > 0 { | 
 | 		pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line) | 
 | 	} else if e.Filename != "" { | 
 | 		pos = fmt.Sprintf("%s: ", e.Filename) | 
 | 	} | 
 |  | 
 | 	var directive string | 
 | 	if e.ModPath != "" { | 
 | 		directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath) | 
 | 	} else if e.Verb != "" { | 
 | 		directive = fmt.Sprintf("%s: ", e.Verb) | 
 | 	} | 
 |  | 
 | 	return pos + directive + e.Err.Error() | 
 | } | 
 |  | 
 | func (e *Error) Unwrap() error { return e.Err } | 
 |  | 
 | func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) { | 
 | 	t, err := parseString(s) | 
 | 	if err != nil { | 
 | 		return "", &Error{ | 
 | 			Verb:    verb, | 
 | 			ModPath: path, | 
 | 			Err: &module.InvalidVersionError{ | 
 | 				Version: *s, | 
 | 				Err:     err, | 
 | 			}, | 
 | 		} | 
 | 	} | 
 | 	if fix != nil { | 
 | 		fixed, err := fix(path, t) | 
 | 		if err != nil { | 
 | 			if err, ok := err.(*module.ModuleError); ok { | 
 | 				return "", &Error{ | 
 | 					Verb:    verb, | 
 | 					ModPath: path, | 
 | 					Err:     err.Err, | 
 | 				} | 
 | 			} | 
 | 			return "", err | 
 | 		} | 
 | 		t = fixed | 
 | 	} else { | 
 | 		cv := module.CanonicalVersion(t) | 
 | 		if cv == "" { | 
 | 			return "", &Error{ | 
 | 				Verb:    verb, | 
 | 				ModPath: path, | 
 | 				Err: &module.InvalidVersionError{ | 
 | 					Version: t, | 
 | 					Err:     errors.New("must be of the form v1.2.3"), | 
 | 				}, | 
 | 			} | 
 | 		} | 
 | 		t = cv | 
 | 	} | 
 | 	*s = t | 
 | 	return *s, nil | 
 | } | 
 |  | 
 | func modulePathMajor(path string) (string, error) { | 
 | 	_, major, ok := module.SplitPathVersion(path) | 
 | 	if !ok { | 
 | 		return "", fmt.Errorf("invalid module path") | 
 | 	} | 
 | 	return major, nil | 
 | } | 
 |  | 
 | func (f *File) Format() ([]byte, error) { | 
 | 	return Format(f.Syntax), nil | 
 | } | 
 |  | 
 | // 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) Cleanup() { | 
 | 	w := 0 | 
 | 	for _, r := range f.Require { | 
 | 		if r.Mod.Path != "" { | 
 | 			f.Require[w] = r | 
 | 			w++ | 
 | 		} | 
 | 	} | 
 | 	f.Require = f.Require[:w] | 
 |  | 
 | 	w = 0 | 
 | 	for _, x := range f.Exclude { | 
 | 		if x.Mod.Path != "" { | 
 | 			f.Exclude[w] = x | 
 | 			w++ | 
 | 		} | 
 | 	} | 
 | 	f.Exclude = f.Exclude[:w] | 
 |  | 
 | 	w = 0 | 
 | 	for _, r := range f.Replace { | 
 | 		if r.Old.Path != "" { | 
 | 			f.Replace[w] = r | 
 | 			w++ | 
 | 		} | 
 | 	} | 
 | 	f.Replace = f.Replace[:w] | 
 |  | 
 | 	w = 0 | 
 | 	for _, r := range f.Retract { | 
 | 		if r.Low != "" || r.High != "" { | 
 | 			f.Retract[w] = r | 
 | 			w++ | 
 | 		} | 
 | 	} | 
 | 	f.Retract = f.Retract[:w] | 
 |  | 
 | 	f.Syntax.Cleanup() | 
 | } | 
 |  | 
 | func (f *File) AddGoStmt(version string) error { | 
 | 	if !GoVersionRE.MatchString(version) { | 
 | 		return fmt.Errorf("invalid language version %q", version) | 
 | 	} | 
 | 	if f.Go == nil { | 
 | 		var hint Expr | 
 | 		if f.Module != nil && f.Module.Syntax != nil { | 
 | 			hint = f.Module.Syntax | 
 | 		} else if f.Syntax == nil { | 
 | 			f.Syntax = new(FileSyntax) | 
 | 		} | 
 | 		f.Go = &Go{ | 
 | 			Version: version, | 
 | 			Syntax:  f.Syntax.addLine(hint, "go", version), | 
 | 		} | 
 | 	} else { | 
 | 		f.Go.Version = version | 
 | 		f.Syntax.updateLine(f.Go.Syntax, "go", version) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // DropGoStmt deletes the go statement from the file. | 
 | func (f *File) DropGoStmt() { | 
 | 	if f.Go != nil { | 
 | 		f.Go.Syntax.markRemoved() | 
 | 		f.Go = nil | 
 | 	} | 
 | } | 
 |  | 
 | // DropToolchainStmt deletes the toolchain statement from the file. | 
 | func (f *File) DropToolchainStmt() { | 
 | 	if f.Toolchain != nil { | 
 | 		f.Toolchain.Syntax.markRemoved() | 
 | 		f.Toolchain = nil | 
 | 	} | 
 | } | 
 |  | 
 | func (f *File) AddToolchainStmt(name string) error { | 
 | 	if !ToolchainRE.MatchString(name) { | 
 | 		return fmt.Errorf("invalid toolchain name %q", name) | 
 | 	} | 
 | 	if f.Toolchain == nil { | 
 | 		var hint Expr | 
 | 		if f.Go != nil && f.Go.Syntax != nil { | 
 | 			hint = f.Go.Syntax | 
 | 		} else if f.Module != nil && f.Module.Syntax != nil { | 
 | 			hint = f.Module.Syntax | 
 | 		} | 
 | 		f.Toolchain = &Toolchain{ | 
 | 			Name:   name, | 
 | 			Syntax: f.Syntax.addLine(hint, "toolchain", name), | 
 | 		} | 
 | 	} else { | 
 | 		f.Toolchain.Name = name | 
 | 		f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // AddRequire 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) AddRequire(path, vers string) error { | 
 | 	need := true | 
 | 	for _, r := range f.Require { | 
 | 		if r.Mod.Path == path { | 
 | 			if need { | 
 | 				r.Mod.Version = vers | 
 | 				f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) | 
 | 				need = false | 
 | 			} else { | 
 | 				r.Syntax.markRemoved() | 
 | 				*r = Require{} | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if need { | 
 | 		f.AddNewRequire(path, vers, false) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // AddNewRequire 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) AddNewRequire(path, vers string, indirect bool) { | 
 | 	line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) | 
 | 	r := &Require{ | 
 | 		Mod:    module.Version{Path: path, Version: vers}, | 
 | 		Syntax: line, | 
 | 	} | 
 | 	r.setIndirect(indirect) | 
 | 	f.Require = append(f.Require, r) | 
 | } | 
 |  | 
 | // SetRequire 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) SetRequire(req []*Require) { | 
 | 	type elem struct { | 
 | 		version  string | 
 | 		indirect bool | 
 | 	} | 
 | 	need := make(map[string]elem) | 
 | 	for _, r := range req { | 
 | 		if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version { | 
 | 			panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version)) | 
 | 		} | 
 | 		need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect} | 
 | 	} | 
 |  | 
 | 	// Update or delete the existing Require entries to preserve | 
 | 	// only the first for each module path in req. | 
 | 	for _, r := range f.Require { | 
 | 		e, ok := need[r.Mod.Path] | 
 | 		if ok { | 
 | 			r.setVersion(e.version) | 
 | 			r.setIndirect(e.indirect) | 
 | 		} else { | 
 | 			r.markRemoved() | 
 | 		} | 
 | 		delete(need, r.Mod.Path) | 
 | 	} | 
 |  | 
 | 	// Add new entries in the last block of the file for any paths that weren't | 
 | 	// already present. | 
 | 	// | 
 | 	// This step is nondeterministic, but the final result will be deterministic | 
 | 	// because we will sort the block. | 
 | 	for path, e := range need { | 
 | 		f.AddNewRequire(path, e.version, e.indirect) | 
 | 	} | 
 |  | 
 | 	f.SortBlocks() | 
 | } | 
 |  | 
 | // SetRequireSeparateIndirect updates the requirements of f to contain the given | 
 | // requirements. Comment contents (except for 'indirect' markings) are retained | 
 | // from the first existing requirement for each module path. Like SetRequire, | 
 | // SetRequireSeparateIndirect adds requirements for new paths in req, | 
 | // updates the version and "// indirect" comment on existing requirements, | 
 | // and deletes requirements on paths not in req. Existing duplicate requirements | 
 | // are deleted. | 
 | // | 
 | // As its name suggests, SetRequireSeparateIndirect puts direct and indirect | 
 | // requirements into two separate blocks, one containing only direct | 
 | // requirements, and the other containing only indirect requirements. | 
 | // SetRequireSeparateIndirect may move requirements between these two blocks | 
 | // when their indirect markings change. However, SetRequireSeparateIndirect | 
 | // 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 a direct-only and indirect-only | 
 | // block. This aids in the transition to separate blocks. | 
 | func (f *File) SetRequireSeparateIndirect(req []*Require) { | 
 | 	// hasComments returns whether a line or block has comments | 
 | 	// other than "indirect". | 
 | 	hasComments := func(c Comments) bool { | 
 | 		return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 || | 
 | 			(len(c.Suffix) == 1 && | 
 | 				strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect") | 
 | 	} | 
 |  | 
 | 	// moveReq adds r to block. If r was in another block, moveReq deletes | 
 | 	// it from that block and transfers its comments. | 
 | 	moveReq := func(r *Require, block *LineBlock) { | 
 | 		var line *Line | 
 | 		if r.Syntax == nil { | 
 | 			line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}} | 
 | 			r.Syntax = line | 
 | 			if r.Indirect { | 
 | 				r.setIndirect(true) | 
 | 			} | 
 | 		} else { | 
 | 			line = new(Line) | 
 | 			*line = *r.Syntax | 
 | 			if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" { | 
 | 				line.Token = line.Token[1:] | 
 | 			} | 
 | 			r.Syntax.Token = nil // Cleanup will delete the old line. | 
 | 			r.Syntax = line | 
 | 		} | 
 | 		line.InBlock = true | 
 | 		block.Line = append(block.Line, line) | 
 | 	} | 
 |  | 
 | 	// Examine existing require lines and blocks. | 
 | 	var ( | 
 | 		// We may insert new requirements into the last uncommented | 
 | 		// direct-only and indirect-only blocks. We may also move requirements | 
 | 		// to the opposite block if their indirect markings change. | 
 | 		lastDirectIndex   = -1 | 
 | 		lastIndirectIndex = -1 | 
 |  | 
 | 		// If there are no direct-only or indirect-only blocks, a new block may | 
 | 		// be inserted after the last require line or block. | 
 | 		lastRequireIndex = -1 | 
 |  | 
 | 		// If there's only one require line or block, and it's uncommented, | 
 | 		// we'll move its requirements to the direct-only or indirect-only blocks. | 
 | 		requireLineOrBlockCount = 0 | 
 |  | 
 | 		// Track the block each requirement belongs to (if any) so we can | 
 | 		// move them later. | 
 | 		lineToBlock = make(map[*Line]*LineBlock) | 
 | 	) | 
 | 	for i, stmt := range f.Syntax.Stmt { | 
 | 		switch stmt := stmt.(type) { | 
 | 		case *Line: | 
 | 			if len(stmt.Token) == 0 || stmt.Token[0] != "require" { | 
 | 				continue | 
 | 			} | 
 | 			lastRequireIndex = i | 
 | 			requireLineOrBlockCount++ | 
 | 			if !hasComments(stmt.Comments) { | 
 | 				if isIndirect(stmt) { | 
 | 					lastIndirectIndex = i | 
 | 				} else { | 
 | 					lastDirectIndex = i | 
 | 				} | 
 | 			} | 
 |  | 
 | 		case *LineBlock: | 
 | 			if len(stmt.Token) == 0 || stmt.Token[0] != "require" { | 
 | 				continue | 
 | 			} | 
 | 			lastRequireIndex = i | 
 | 			requireLineOrBlockCount++ | 
 | 			allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) | 
 | 			allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) | 
 | 			for _, line := range stmt.Line { | 
 | 				lineToBlock[line] = stmt | 
 | 				if hasComments(line.Comments) { | 
 | 					allDirect = false | 
 | 					allIndirect = false | 
 | 				} else if isIndirect(line) { | 
 | 					allDirect = false | 
 | 				} else { | 
 | 					allIndirect = false | 
 | 				} | 
 | 			} | 
 | 			if allDirect { | 
 | 				lastDirectIndex = i | 
 | 			} | 
 | 			if allIndirect { | 
 | 				lastIndirectIndex = i | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	oneFlatUncommentedBlock := requireLineOrBlockCount == 1 && | 
 | 		!hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment()) | 
 |  | 
 | 	// Create direct and indirect blocks if needed. Convert lines into blocks | 
 | 	// if needed. If we end up with an empty block or a one-line block, | 
 | 	// Cleanup will delete it or convert it to a line later. | 
 | 	insertBlock := func(i int) *LineBlock { | 
 | 		block := &LineBlock{Token: []string{"require"}} | 
 | 		f.Syntax.Stmt = append(f.Syntax.Stmt, nil) | 
 | 		copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:]) | 
 | 		f.Syntax.Stmt[i] = block | 
 | 		return block | 
 | 	} | 
 |  | 
 | 	ensureBlock := func(i int) *LineBlock { | 
 | 		switch stmt := f.Syntax.Stmt[i].(type) { | 
 | 		case *LineBlock: | 
 | 			return stmt | 
 | 		case *Line: | 
 | 			block := &LineBlock{ | 
 | 				Token: []string{"require"}, | 
 | 				Line:  []*Line{stmt}, | 
 | 			} | 
 | 			stmt.Token = stmt.Token[1:] // remove "require" | 
 | 			stmt.InBlock = true | 
 | 			f.Syntax.Stmt[i] = block | 
 | 			return block | 
 | 		default: | 
 | 			panic(fmt.Sprintf("unexpected statement: %v", stmt)) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	var lastDirectBlock *LineBlock | 
 | 	if lastDirectIndex < 0 { | 
 | 		if lastIndirectIndex >= 0 { | 
 | 			lastDirectIndex = lastIndirectIndex | 
 | 			lastIndirectIndex++ | 
 | 		} else if lastRequireIndex >= 0 { | 
 | 			lastDirectIndex = lastRequireIndex + 1 | 
 | 		} else { | 
 | 			lastDirectIndex = len(f.Syntax.Stmt) | 
 | 		} | 
 | 		lastDirectBlock = insertBlock(lastDirectIndex) | 
 | 	} else { | 
 | 		lastDirectBlock = ensureBlock(lastDirectIndex) | 
 | 	} | 
 |  | 
 | 	var lastIndirectBlock *LineBlock | 
 | 	if lastIndirectIndex < 0 { | 
 | 		lastIndirectIndex = lastDirectIndex + 1 | 
 | 		lastIndirectBlock = insertBlock(lastIndirectIndex) | 
 | 	} else { | 
 | 		lastIndirectBlock = ensureBlock(lastIndirectIndex) | 
 | 	} | 
 |  | 
 | 	// Delete requirements we don't want anymore. | 
 | 	// Update versions and indirect comments on requirements we want to keep. | 
 | 	// If a requirement is in last{Direct,Indirect}Block with the wrong | 
 | 	// indirect marking after this, or if the requirement is in an single | 
 | 	// uncommented mixed block (oneFlatUncommentedBlock), move it to the | 
 | 	// correct block. | 
 | 	// | 
 | 	// Some blocks may be empty after this. Cleanup will remove them. | 
 | 	need := make(map[string]*Require) | 
 | 	for _, r := range req { | 
 | 		need[r.Mod.Path] = r | 
 | 	} | 
 | 	have := make(map[string]*Require) | 
 | 	for _, r := range f.Require { | 
 | 		path := r.Mod.Path | 
 | 		if need[path] == nil || have[path] != nil { | 
 | 			// Requirement not needed, or duplicate requirement. Delete. | 
 | 			r.markRemoved() | 
 | 			continue | 
 | 		} | 
 | 		have[r.Mod.Path] = r | 
 | 		r.setVersion(need[path].Mod.Version) | 
 | 		r.setIndirect(need[path].Indirect) | 
 | 		if need[path].Indirect && | 
 | 			(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) { | 
 | 			moveReq(r, lastIndirectBlock) | 
 | 		} else if !need[path].Indirect && | 
 | 			(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) { | 
 | 			moveReq(r, lastDirectBlock) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// Add new requirements. | 
 | 	for path, r := range need { | 
 | 		if have[path] == nil { | 
 | 			if r.Indirect { | 
 | 				moveReq(r, lastIndirectBlock) | 
 | 			} else { | 
 | 				moveReq(r, lastDirectBlock) | 
 | 			} | 
 | 			f.Require = append(f.Require, r) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	f.SortBlocks() | 
 | } | 
 |  | 
 | func (f *File) DropRequire(path string) error { | 
 | 	for _, r := range f.Require { | 
 | 		if r.Mod.Path == path { | 
 | 			r.Syntax.markRemoved() | 
 | 			*r = Require{} | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // AddExclude adds a exclude statement to the mod file. Errors if the provided | 
 | // version is not a canonical version string | 
 | func (f *File) AddExclude(path, vers string) error { | 
 | 	if err := checkCanonicalVersion(path, vers); err != nil { | 
 | 		return err | 
 | 	} | 
 |  | 
 | 	var hint *Line | 
 | 	for _, x := range f.Exclude { | 
 | 		if x.Mod.Path == path && x.Mod.Version == vers { | 
 | 			return nil | 
 | 		} | 
 | 		if x.Mod.Path == path { | 
 | 			hint = x.Syntax | 
 | 		} | 
 | 	} | 
 |  | 
 | 	f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) | 
 | 	return nil | 
 | } | 
 |  | 
 | func (f *File) DropExclude(path, vers string) error { | 
 | 	for _, x := range f.Exclude { | 
 | 		if x.Mod.Path == path && x.Mod.Version == vers { | 
 | 			x.Syntax.markRemoved() | 
 | 			*x = Exclude{} | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { | 
 | 	return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) | 
 | } | 
 |  | 
 | func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error { | 
 | 	need := true | 
 | 	old := module.Version{Path: oldPath, Version: oldVers} | 
 | 	new := module.Version{Path: newPath, Version: newVers} | 
 | 	tokens := []string{"replace", AutoQuote(oldPath)} | 
 | 	if oldVers != "" { | 
 | 		tokens = append(tokens, oldVers) | 
 | 	} | 
 | 	tokens = append(tokens, "=>", AutoQuote(newPath)) | 
 | 	if newVers != "" { | 
 | 		tokens = append(tokens, newVers) | 
 | 	} | 
 |  | 
 | 	var hint *Line | 
 | 	for _, r := range *replace { | 
 | 		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { | 
 | 			if need { | 
 | 				// Found replacement for old; update to use new. | 
 | 				r.New = new | 
 | 				syntax.updateLine(r.Syntax, tokens...) | 
 | 				need = false | 
 | 				continue | 
 | 			} | 
 | 			// Already added; delete other replacements for same. | 
 | 			r.Syntax.markRemoved() | 
 | 			*r = Replace{} | 
 | 		} | 
 | 		if r.Old.Path == oldPath { | 
 | 			hint = r.Syntax | 
 | 		} | 
 | 	} | 
 | 	if need { | 
 | 		*replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)}) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func (f *File) DropReplace(oldPath, oldVers string) error { | 
 | 	for _, r := range f.Replace { | 
 | 		if r.Old.Path == oldPath && r.Old.Version == oldVers { | 
 | 			r.Syntax.markRemoved() | 
 | 			*r = Replace{} | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // AddRetract adds a retract statement to the mod file. Errors if the provided | 
 | // version interval does not consist of canonical version strings | 
 | func (f *File) AddRetract(vi VersionInterval, rationale string) error { | 
 | 	var path string | 
 | 	if f.Module != nil { | 
 | 		path = f.Module.Mod.Path | 
 | 	} | 
 | 	if err := checkCanonicalVersion(path, vi.High); err != nil { | 
 | 		return err | 
 | 	} | 
 | 	if err := checkCanonicalVersion(path, vi.Low); err != nil { | 
 | 		return err | 
 | 	} | 
 |  | 
 | 	r := &Retract{ | 
 | 		VersionInterval: vi, | 
 | 	} | 
 | 	if vi.Low == vi.High { | 
 | 		r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low)) | 
 | 	} else { | 
 | 		r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]") | 
 | 	} | 
 | 	if rationale != "" { | 
 | 		for _, line := range strings.Split(rationale, "\n") { | 
 | 			com := Comment{Token: "// " + line} | 
 | 			r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com) | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func (f *File) DropRetract(vi VersionInterval) error { | 
 | 	for _, r := range f.Retract { | 
 | 		if r.VersionInterval == vi { | 
 | 			r.Syntax.markRemoved() | 
 | 			*r = Retract{} | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func (f *File) SortBlocks() { | 
 | 	f.removeDups() // otherwise sorting is unsafe | 
 |  | 
 | 	// semanticSortForExcludeVersionV is the Go version (plus leading "v") at which | 
 | 	// lines in exclude blocks start to use semantic sort instead of lexicographic sort. | 
 | 	// See go.dev/issue/60028. | 
 | 	const semanticSortForExcludeVersionV = "v1.21" | 
 | 	useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0 | 
 |  | 
 | 	for _, stmt := range f.Syntax.Stmt { | 
 | 		block, ok := stmt.(*LineBlock) | 
 | 		if !ok { | 
 | 			continue | 
 | 		} | 
 | 		less := lineLess | 
 | 		if block.Token[0] == "exclude" && useSemanticSortForExclude { | 
 | 			less = lineExcludeLess | 
 | 		} else if block.Token[0] == "retract" { | 
 | 			less = lineRetractLess | 
 | 		} | 
 | 		sort.SliceStable(block.Line, func(i, j int) bool { | 
 | 			return less(block.Line[i], block.Line[j]) | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | // 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 (f *File) removeDups() { | 
 | 	removeDups(f.Syntax, &f.Exclude, &f.Replace) | 
 | } | 
 |  | 
 | func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) { | 
 | 	kill := make(map[*Line]bool) | 
 |  | 
 | 	// Remove duplicate excludes. | 
 | 	if exclude != nil { | 
 | 		haveExclude := make(map[module.Version]bool) | 
 | 		for _, x := range *exclude { | 
 | 			if haveExclude[x.Mod] { | 
 | 				kill[x.Syntax] = true | 
 | 				continue | 
 | 			} | 
 | 			haveExclude[x.Mod] = true | 
 | 		} | 
 | 		var excl []*Exclude | 
 | 		for _, x := range *exclude { | 
 | 			if !kill[x.Syntax] { | 
 | 				excl = append(excl, x) | 
 | 			} | 
 | 		} | 
 | 		*exclude = excl | 
 | 	} | 
 |  | 
 | 	// Remove duplicate replacements. | 
 | 	// Later replacements take priority over earlier ones. | 
 | 	haveReplace := make(map[module.Version]bool) | 
 | 	for i := len(*replace) - 1; i >= 0; i-- { | 
 | 		x := (*replace)[i] | 
 | 		if haveReplace[x.Old] { | 
 | 			kill[x.Syntax] = true | 
 | 			continue | 
 | 		} | 
 | 		haveReplace[x.Old] = true | 
 | 	} | 
 | 	var repl []*Replace | 
 | 	for _, x := range *replace { | 
 | 		if !kill[x.Syntax] { | 
 | 			repl = append(repl, x) | 
 | 		} | 
 | 	} | 
 | 	*replace = repl | 
 |  | 
 | 	// Duplicate require and retract directives are not removed. | 
 |  | 
 | 	// Drop killed statements from the syntax tree. | 
 | 	var stmts []Expr | 
 | 	for _, stmt := range syntax.Stmt { | 
 | 		switch stmt := stmt.(type) { | 
 | 		case *Line: | 
 | 			if kill[stmt] { | 
 | 				continue | 
 | 			} | 
 | 		case *LineBlock: | 
 | 			var lines []*Line | 
 | 			for _, line := range stmt.Line { | 
 | 				if !kill[line] { | 
 | 					lines = append(lines, line) | 
 | 				} | 
 | 			} | 
 | 			stmt.Line = lines | 
 | 			if len(lines) == 0 { | 
 | 				continue | 
 | 			} | 
 | 		} | 
 | 		stmts = append(stmts, stmt) | 
 | 	} | 
 | 	syntax.Stmt = stmts | 
 | } | 
 |  | 
 | // lineLess returns whether li should be sorted before lj. It sorts | 
 | // lexicographically without assigning any special meaning to tokens. | 
 | func lineLess(li, lj *Line) bool { | 
 | 	for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { | 
 | 		if li.Token[k] != lj.Token[k] { | 
 | 			return li.Token[k] < lj.Token[k] | 
 | 		} | 
 | 	} | 
 | 	return len(li.Token) < len(lj.Token) | 
 | } | 
 |  | 
 | // lineExcludeLess reports whether li should be sorted before lj for lines in | 
 | // an "exclude" block. | 
 | func lineExcludeLess(li, lj *Line) bool { | 
 | 	if len(li.Token) != 2 || len(lj.Token) != 2 { | 
 | 		// Not a known exclude specification. | 
 | 		// Fall back to sorting lexicographically. | 
 | 		return lineLess(li, lj) | 
 | 	} | 
 | 	// An exclude specification has two tokens: ModulePath and Version. | 
 | 	// Compare module path by string order and version by semver rules. | 
 | 	if pi, pj := li.Token[0], lj.Token[0]; pi != pj { | 
 | 		return pi < pj | 
 | 	} | 
 | 	return semver.Compare(li.Token[1], lj.Token[1]) < 0 | 
 | } | 
 |  | 
 | // 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 lineRetractLess(li, lj *Line) bool { | 
 | 	interval := func(l *Line) VersionInterval { | 
 | 		if len(l.Token) == 1 { | 
 | 			return VersionInterval{Low: l.Token[0], High: l.Token[0]} | 
 | 		} else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" { | 
 | 			return VersionInterval{Low: l.Token[1], High: l.Token[3]} | 
 | 		} else { | 
 | 			// Line in unknown format. Treat as an invalid version. | 
 | 			return VersionInterval{} | 
 | 		} | 
 | 	} | 
 | 	vii := interval(li) | 
 | 	vij := interval(lj) | 
 | 	if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 { | 
 | 		return cmp > 0 | 
 | 	} | 
 | 	return semver.Compare(vii.High, vij.High) > 0 | 
 | } | 
 |  | 
 | // checkCanonicalVersion returns a non-nil error if vers is not a canonical | 
 | // version string or does not match the major version of path. | 
 | // | 
 | // If path is non-empty, the error text suggests a format with a major version | 
 | // corresponding to the path. | 
 | func checkCanonicalVersion(path, vers string) error { | 
 | 	_, pathMajor, pathMajorOk := module.SplitPathVersion(path) | 
 |  | 
 | 	if vers == "" || vers != module.CanonicalVersion(vers) { | 
 | 		if pathMajor == "" { | 
 | 			return &module.InvalidVersionError{ | 
 | 				Version: vers, | 
 | 				Err:     fmt.Errorf("must be of the form v1.2.3"), | 
 | 			} | 
 | 		} | 
 | 		return &module.InvalidVersionError{ | 
 | 			Version: vers, | 
 | 			Err:     fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)), | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if pathMajorOk { | 
 | 		if err := module.CheckPathMajor(vers, pathMajor); err != nil { | 
 | 			if pathMajor == "" { | 
 | 				// In this context, the user probably wrote "v2.3.4" when they meant | 
 | 				// "v2.3.4+incompatible". Suggest that instead of "v0 or v1". | 
 | 				return &module.InvalidVersionError{ | 
 | 					Version: vers, | 
 | 					Err:     fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)), | 
 | 				} | 
 | 			} | 
 | 			return err | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return nil | 
 | } |