Source file src/cmd/vendor/golang.org/x/mod/modfile/rule.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package modfile implements a parser and formatter for go.mod files.
     6  //
     7  // The go.mod syntax is described in
     8  // https://pkg.go.dev/cmd/go/#hdr-The_go_mod_file.
     9  //
    10  // The [Parse] and [ParseLax] functions both parse a go.mod file and return an
    11  // abstract syntax tree. ParseLax ignores unknown statements and may be used to
    12  // parse go.mod files that may have been developed with newer versions of Go.
    13  //
    14  // The [File] struct returned by Parse and ParseLax represent an abstract
    15  // go.mod file. File has several methods like [File.AddNewRequire] and
    16  // [File.DropReplace] that can be used to programmatically edit a file.
    17  //
    18  // The [Format] function formats a File back to a byte slice which can be
    19  // written to a file.
    20  package modfile
    21  
    22  import (
    23  	"cmp"
    24  	"errors"
    25  	"fmt"
    26  	"path/filepath"
    27  	"slices"
    28  	"strconv"
    29  	"strings"
    30  	"unicode"
    31  
    32  	"golang.org/x/mod/internal/lazyregexp"
    33  	"golang.org/x/mod/module"
    34  	"golang.org/x/mod/semver"
    35  )
    36  
    37  // A File is the parsed, interpreted form of a go.mod file.
    38  type File struct {
    39  	Module    *Module
    40  	Go        *Go
    41  	Toolchain *Toolchain
    42  	Godebug   []*Godebug
    43  	Require   []*Require
    44  	Exclude   []*Exclude
    45  	Replace   []*Replace
    46  	Retract   []*Retract
    47  	Tool      []*Tool
    48  	Ignore    []*Ignore
    49  
    50  	Syntax *FileSyntax
    51  }
    52  
    53  // A Module is the module statement.
    54  type Module struct {
    55  	Mod        module.Version
    56  	Deprecated string
    57  	Syntax     *Line
    58  }
    59  
    60  // A Go is the go statement.
    61  type Go struct {
    62  	Version string // "1.23"
    63  	Syntax  *Line
    64  }
    65  
    66  // A Toolchain is the toolchain statement.
    67  type Toolchain struct {
    68  	Name   string // "go1.21rc1"
    69  	Syntax *Line
    70  }
    71  
    72  // A Godebug is a single godebug key=value statement.
    73  type Godebug struct {
    74  	Key    string
    75  	Value  string
    76  	Syntax *Line
    77  }
    78  
    79  // An Exclude is a single exclude statement.
    80  type Exclude struct {
    81  	Mod    module.Version
    82  	Syntax *Line
    83  }
    84  
    85  // A Replace is a single replace statement.
    86  type Replace struct {
    87  	Old    module.Version
    88  	New    module.Version
    89  	Syntax *Line
    90  }
    91  
    92  // A Retract is a single retract statement.
    93  type Retract struct {
    94  	VersionInterval
    95  	Rationale string
    96  	Syntax    *Line
    97  }
    98  
    99  // A Tool is a single tool statement.
   100  type Tool struct {
   101  	Path   string
   102  	Syntax *Line
   103  }
   104  
   105  // An Ignore is a single ignore statement.
   106  type Ignore struct {
   107  	Path   string
   108  	Syntax *Line
   109  }
   110  
   111  // A VersionInterval represents a range of versions with upper and lower bounds.
   112  // Intervals are closed: both bounds are included. When Low is equal to High,
   113  // the interval may refer to a single version ('v1.2.3') or an interval
   114  // ('[v1.2.3, v1.2.3]'); both have the same representation.
   115  type VersionInterval struct {
   116  	Low, High string
   117  }
   118  
   119  // A Require is a single require statement.
   120  type Require struct {
   121  	Mod      module.Version
   122  	Indirect bool // has "// indirect" comment
   123  	Syntax   *Line
   124  }
   125  
   126  func (r *Require) markRemoved() {
   127  	r.Syntax.markRemoved()
   128  	*r = Require{}
   129  }
   130  
   131  func (r *Require) setVersion(v string) {
   132  	r.Mod.Version = v
   133  
   134  	if line := r.Syntax; len(line.Token) > 0 {
   135  		if line.InBlock {
   136  			// If the line is preceded by an empty line, remove it; see
   137  			// https://golang.org/issue/33779.
   138  			if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
   139  				line.Comments.Before = line.Comments.Before[:0]
   140  			}
   141  			if len(line.Token) >= 2 { // example.com v1.2.3
   142  				line.Token[1] = v
   143  			}
   144  		} else {
   145  			if len(line.Token) >= 3 { // require example.com v1.2.3
   146  				line.Token[2] = v
   147  			}
   148  		}
   149  	}
   150  }
   151  
   152  // setIndirect sets line to have (or not have) a "// indirect" comment.
   153  func (r *Require) setIndirect(indirect bool) {
   154  	r.Indirect = indirect
   155  	line := r.Syntax
   156  	if isIndirect(line) == indirect {
   157  		return
   158  	}
   159  	if indirect {
   160  		// Adding comment.
   161  		if len(line.Suffix) == 0 {
   162  			// New comment.
   163  			line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
   164  			return
   165  		}
   166  
   167  		com := &line.Suffix[0]
   168  		text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash)))
   169  		if text == "" {
   170  			// Empty comment.
   171  			com.Token = "// indirect"
   172  			return
   173  		}
   174  
   175  		// Insert at beginning of existing comment.
   176  		com.Token = "// indirect; " + text
   177  		return
   178  	}
   179  
   180  	// Removing comment.
   181  	f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
   182  	if f == "indirect" {
   183  		// Remove whole comment.
   184  		line.Suffix = nil
   185  		return
   186  	}
   187  
   188  	// Remove comment prefix.
   189  	com := &line.Suffix[0]
   190  	i := strings.Index(com.Token, "indirect;")
   191  	com.Token = "//" + com.Token[i+len("indirect;"):]
   192  }
   193  
   194  // isIndirect reports whether line has a "// indirect" comment,
   195  // meaning it is in go.mod only for its effect on indirect dependencies,
   196  // so that it can be dropped entirely once the effective version of the
   197  // indirect dependency reaches the given minimum version.
   198  func isIndirect(line *Line) bool {
   199  	if len(line.Suffix) == 0 {
   200  		return false
   201  	}
   202  	f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
   203  	return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;")
   204  }
   205  
   206  func (f *File) AddModuleStmt(path string) error {
   207  	if f.Syntax == nil {
   208  		f.Syntax = new(FileSyntax)
   209  	}
   210  	if f.Module == nil {
   211  		f.Module = &Module{
   212  			Mod:    module.Version{Path: path},
   213  			Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
   214  		}
   215  	} else {
   216  		f.Module.Mod.Path = path
   217  		f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
   218  	}
   219  	return nil
   220  }
   221  
   222  func (f *File) AddComment(text string) {
   223  	if f.Syntax == nil {
   224  		f.Syntax = new(FileSyntax)
   225  	}
   226  	f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
   227  		Comments: Comments{
   228  			Before: []Comment{
   229  				{
   230  					Token: text,
   231  				},
   232  			},
   233  		},
   234  	})
   235  }
   236  
   237  type VersionFixer func(path, version string) (string, error)
   238  
   239  // errDontFix is returned by a VersionFixer to indicate the version should be
   240  // left alone, even if it's not canonical.
   241  var dontFixRetract VersionFixer = func(_, vers string) (string, error) {
   242  	return vers, nil
   243  }
   244  
   245  // Parse parses and returns a go.mod file.
   246  //
   247  // file is the name of the file, used in positions and errors.
   248  //
   249  // data is the content of the file.
   250  //
   251  // fix is an optional function that canonicalizes module versions.
   252  // If fix is nil, all module versions must be canonical ([module.CanonicalVersion]
   253  // must return the same string).
   254  func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
   255  	return parseToFile(file, data, fix, true)
   256  }
   257  
   258  // ParseLax is like Parse but ignores unknown statements.
   259  // It is used when parsing go.mod files other than the main module,
   260  // under the theory that most statement types we add in the future will
   261  // only apply in the main module, like exclude and replace,
   262  // and so we get better gradual deployments if old go commands
   263  // simply ignore those statements when found in go.mod files
   264  // in dependencies.
   265  func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
   266  	return parseToFile(file, data, fix, false)
   267  }
   268  
   269  func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) {
   270  	fs, err := parse(file, data)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	f := &File{
   275  		Syntax: fs,
   276  	}
   277  	var errs ErrorList
   278  
   279  	// fix versions in retract directives after the file is parsed.
   280  	// We need the module path to fix versions, and it might be at the end.
   281  	defer func() {
   282  		oldLen := len(errs)
   283  		f.fixRetract(fix, &errs)
   284  		if len(errs) > oldLen {
   285  			parsed, err = nil, errs
   286  		}
   287  	}()
   288  
   289  	for _, x := range fs.Stmt {
   290  		switch x := x.(type) {
   291  		case *Line:
   292  			f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
   293  
   294  		case *LineBlock:
   295  			if len(x.Token) > 1 {
   296  				if strict {
   297  					errs = append(errs, Error{
   298  						Filename: file,
   299  						Pos:      x.Start,
   300  						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
   301  					})
   302  				}
   303  				continue
   304  			}
   305  			switch x.Token[0] {
   306  			default:
   307  				if strict {
   308  					errs = append(errs, Error{
   309  						Filename: file,
   310  						Pos:      x.Start,
   311  						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
   312  					})
   313  				}
   314  				continue
   315  			case "module", "godebug", "require", "exclude", "replace", "retract", "tool", "ignore":
   316  				for _, l := range x.Line {
   317  					f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
   318  				}
   319  			}
   320  		}
   321  	}
   322  
   323  	if len(errs) > 0 {
   324  		return nil, errs
   325  	}
   326  	return f, nil
   327  }
   328  
   329  var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)
   330  
   331  var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
   332  
   333  // Toolchains must be named beginning with `go1`,
   334  // like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted.
   335  // Note that this regexp is a much looser condition than go/version.IsValid,
   336  // for forward compatibility.
   337  // (This code has to be work to identify new toolchains even if we tweak the syntax in the future.)
   338  var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`)
   339  
   340  func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
   341  	// If strict is false, this module is a dependency.
   342  	// We ignore all unknown directives as well as main-module-only
   343  	// directives like replace and exclude. It will work better for
   344  	// forward compatibility if we can depend on modules that have unknown
   345  	// statements (presumed relevant only when acting as the main module)
   346  	// and simply ignore those statements.
   347  	if !strict {
   348  		switch verb {
   349  		case "go", "module", "retract", "require", "ignore":
   350  			// want these even for dependency go.mods
   351  		default:
   352  			return
   353  		}
   354  	}
   355  
   356  	wrapModPathError := func(modPath string, err error) {
   357  		*errs = append(*errs, Error{
   358  			Filename: f.Syntax.Name,
   359  			Pos:      line.Start,
   360  			ModPath:  modPath,
   361  			Verb:     verb,
   362  			Err:      err,
   363  		})
   364  	}
   365  	wrapError := func(err error) {
   366  		*errs = append(*errs, Error{
   367  			Filename: f.Syntax.Name,
   368  			Pos:      line.Start,
   369  			Err:      err,
   370  		})
   371  	}
   372  	errorf := func(format string, args ...any) {
   373  		wrapError(fmt.Errorf(format, args...))
   374  	}
   375  
   376  	switch verb {
   377  	default:
   378  		errorf("unknown directive: %s", verb)
   379  
   380  	case "go":
   381  		if f.Go != nil {
   382  			errorf("repeated go statement")
   383  			return
   384  		}
   385  		if len(args) != 1 {
   386  			errorf("go directive expects exactly one argument")
   387  			return
   388  		} else if !GoVersionRE.MatchString(args[0]) {
   389  			fixed := false
   390  			if !strict {
   391  				if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil {
   392  					args[0] = m[1]
   393  					fixed = true
   394  				}
   395  			}
   396  			if !fixed {
   397  				errorf("invalid go version '%s': must match format 1.23.0", args[0])
   398  				return
   399  			}
   400  		}
   401  
   402  		f.Go = &Go{Syntax: line}
   403  		f.Go.Version = args[0]
   404  
   405  	case "toolchain":
   406  		if f.Toolchain != nil {
   407  			errorf("repeated toolchain statement")
   408  			return
   409  		}
   410  		if len(args) != 1 {
   411  			errorf("toolchain directive expects exactly one argument")
   412  			return
   413  		} else if !ToolchainRE.MatchString(args[0]) {
   414  			errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0])
   415  			return
   416  		}
   417  		f.Toolchain = &Toolchain{Syntax: line}
   418  		f.Toolchain.Name = args[0]
   419  
   420  	case "module":
   421  		if f.Module != nil {
   422  			errorf("repeated module statement")
   423  			return
   424  		}
   425  		deprecated := parseDeprecation(block, line)
   426  		f.Module = &Module{
   427  			Syntax:     line,
   428  			Deprecated: deprecated,
   429  		}
   430  		if len(args) != 1 {
   431  			errorf("usage: module module/path")
   432  			return
   433  		}
   434  		s, err := parseString(&args[0])
   435  		if err != nil {
   436  			errorf("invalid quoted string: %v", err)
   437  			return
   438  		}
   439  		f.Module.Mod = module.Version{Path: s}
   440  
   441  	case "godebug":
   442  		if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") {
   443  			errorf("usage: godebug key=value")
   444  			return
   445  		}
   446  		key, value, ok := strings.Cut(args[0], "=")
   447  		if !ok {
   448  			errorf("usage: godebug key=value")
   449  			return
   450  		}
   451  		f.Godebug = append(f.Godebug, &Godebug{
   452  			Key:    key,
   453  			Value:  value,
   454  			Syntax: line,
   455  		})
   456  
   457  	case "require", "exclude":
   458  		if len(args) != 2 {
   459  			errorf("usage: %s module/path v1.2.3", verb)
   460  			return
   461  		}
   462  		s, err := parseString(&args[0])
   463  		if err != nil {
   464  			errorf("invalid quoted string: %v", err)
   465  			return
   466  		}
   467  		v, err := parseVersion(verb, s, &args[1], fix)
   468  		if err != nil {
   469  			wrapError(err)
   470  			return
   471  		}
   472  		pathMajor, err := modulePathMajor(s)
   473  		if err != nil {
   474  			wrapError(err)
   475  			return
   476  		}
   477  		if err := module.CheckPathMajor(v, pathMajor); err != nil {
   478  			wrapModPathError(s, err)
   479  			return
   480  		}
   481  		if verb == "require" {
   482  			f.Require = append(f.Require, &Require{
   483  				Mod:      module.Version{Path: s, Version: v},
   484  				Syntax:   line,
   485  				Indirect: isIndirect(line),
   486  			})
   487  		} else {
   488  			f.Exclude = append(f.Exclude, &Exclude{
   489  				Mod:    module.Version{Path: s, Version: v},
   490  				Syntax: line,
   491  			})
   492  		}
   493  
   494  	case "replace":
   495  		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
   496  		if wrappederr != nil {
   497  			*errs = append(*errs, *wrappederr)
   498  			return
   499  		}
   500  		f.Replace = append(f.Replace, replace)
   501  
   502  	case "retract":
   503  		rationale := parseDirectiveComment(block, line)
   504  		vi, err := parseVersionInterval(verb, "", &args, dontFixRetract)
   505  		if err != nil {
   506  			if strict {
   507  				wrapError(err)
   508  				return
   509  			} else {
   510  				// Only report errors parsing intervals in the main module. We may
   511  				// support additional syntax in the future, such as open and half-open
   512  				// intervals. Those can't be supported now, because they break the
   513  				// go.mod parser, even in lax mode.
   514  				return
   515  			}
   516  		}
   517  		if len(args) > 0 && strict {
   518  			// In the future, there may be additional information after the version.
   519  			errorf("unexpected token after version: %q", args[0])
   520  			return
   521  		}
   522  		retract := &Retract{
   523  			VersionInterval: vi,
   524  			Rationale:       rationale,
   525  			Syntax:          line,
   526  		}
   527  		f.Retract = append(f.Retract, retract)
   528  
   529  	case "tool":
   530  		if len(args) != 1 {
   531  			errorf("tool directive expects exactly one argument")
   532  			return
   533  		}
   534  		s, err := parseString(&args[0])
   535  		if err != nil {
   536  			errorf("invalid quoted string: %v", err)
   537  			return
   538  		}
   539  		f.Tool = append(f.Tool, &Tool{
   540  			Path:   s,
   541  			Syntax: line,
   542  		})
   543  
   544  	case "ignore":
   545  		if len(args) != 1 {
   546  			errorf("ignore directive expects exactly one argument")
   547  			return
   548  		}
   549  		s, err := parseString(&args[0])
   550  		if err != nil {
   551  			errorf("invalid quoted string: %v", err)
   552  			return
   553  		}
   554  		f.Ignore = append(f.Ignore, &Ignore{
   555  			Path:   s,
   556  			Syntax: line,
   557  		})
   558  	}
   559  }
   560  
   561  func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) {
   562  	wrapModPathError := func(modPath string, err error) *Error {
   563  		return &Error{
   564  			Filename: filename,
   565  			Pos:      line.Start,
   566  			ModPath:  modPath,
   567  			Verb:     verb,
   568  			Err:      err,
   569  		}
   570  	}
   571  	wrapError := func(err error) *Error {
   572  		return &Error{
   573  			Filename: filename,
   574  			Pos:      line.Start,
   575  			Err:      err,
   576  		}
   577  	}
   578  	errorf := func(format string, args ...any) *Error {
   579  		return wrapError(fmt.Errorf(format, args...))
   580  	}
   581  
   582  	arrow := 2
   583  	if len(args) >= 2 && args[1] == "=>" {
   584  		arrow = 1
   585  	}
   586  	if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
   587  		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)
   588  	}
   589  	s, err := parseString(&args[0])
   590  	if err != nil {
   591  		return nil, errorf("invalid quoted string: %v", err)
   592  	}
   593  	pathMajor, err := modulePathMajor(s)
   594  	if err != nil {
   595  		return nil, wrapModPathError(s, err)
   596  
   597  	}
   598  	var v string
   599  	if arrow == 2 {
   600  		v, err = parseVersion(verb, s, &args[1], fix)
   601  		if err != nil {
   602  			return nil, wrapError(err)
   603  		}
   604  		if err := module.CheckPathMajor(v, pathMajor); err != nil {
   605  			return nil, wrapModPathError(s, err)
   606  		}
   607  	}
   608  	ns, err := parseString(&args[arrow+1])
   609  	if err != nil {
   610  		return nil, errorf("invalid quoted string: %v", err)
   611  	}
   612  	nv := ""
   613  	if len(args) == arrow+2 {
   614  		if !IsDirectoryPath(ns) {
   615  			if strings.Contains(ns, "@") {
   616  				return nil, errorf("replacement module must match format 'path version', not 'path@version'")
   617  			}
   618  			return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)")
   619  		}
   620  		if filepath.Separator == '/' && strings.Contains(ns, `\`) {
   621  			return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
   622  		}
   623  	}
   624  	if len(args) == arrow+3 {
   625  		nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
   626  		if err != nil {
   627  			return nil, wrapError(err)
   628  		}
   629  		if IsDirectoryPath(ns) {
   630  			return nil, errorf("replacement module directory path %q cannot have version", ns)
   631  		}
   632  	}
   633  	return &Replace{
   634  		Old:    module.Version{Path: s, Version: v},
   635  		New:    module.Version{Path: ns, Version: nv},
   636  		Syntax: line,
   637  	}, nil
   638  }
   639  
   640  // fixRetract applies fix to each retract directive in f, appending any errors
   641  // to errs.
   642  //
   643  // Most versions are fixed as we parse the file, but for retract directives,
   644  // the relevant module path is the one specified with the module directive,
   645  // and that might appear at the end of the file (or not at all).
   646  func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
   647  	if fix == nil {
   648  		return
   649  	}
   650  	path := ""
   651  	if f.Module != nil {
   652  		path = f.Module.Mod.Path
   653  	}
   654  	var r *Retract
   655  	wrapError := func(err error) {
   656  		*errs = append(*errs, Error{
   657  			Filename: f.Syntax.Name,
   658  			Pos:      r.Syntax.Start,
   659  			Err:      err,
   660  		})
   661  	}
   662  
   663  	for _, r = range f.Retract {
   664  		if path == "" {
   665  			wrapError(errors.New("no module directive found, so retract cannot be used"))
   666  			return // only print the first one of these
   667  		}
   668  
   669  		args := r.Syntax.Token
   670  		if args[0] == "retract" {
   671  			args = args[1:]
   672  		}
   673  		vi, err := parseVersionInterval("retract", path, &args, fix)
   674  		if err != nil {
   675  			wrapError(err)
   676  		}
   677  		r.VersionInterval = vi
   678  	}
   679  }
   680  
   681  func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
   682  	wrapError := func(err error) {
   683  		*errs = append(*errs, Error{
   684  			Filename: f.Syntax.Name,
   685  			Pos:      line.Start,
   686  			Err:      err,
   687  		})
   688  	}
   689  	errorf := func(format string, args ...any) {
   690  		wrapError(fmt.Errorf(format, args...))
   691  	}
   692  
   693  	switch verb {
   694  	default:
   695  		errorf("unknown directive: %s", verb)
   696  
   697  	case "go":
   698  		if f.Go != nil {
   699  			errorf("repeated go statement")
   700  			return
   701  		}
   702  		if len(args) != 1 {
   703  			errorf("go directive expects exactly one argument")
   704  			return
   705  		} else if !GoVersionRE.MatchString(args[0]) {
   706  			errorf("invalid go version '%s': must match format 1.23.0", args[0])
   707  			return
   708  		}
   709  
   710  		f.Go = &Go{Syntax: line}
   711  		f.Go.Version = args[0]
   712  
   713  	case "toolchain":
   714  		if f.Toolchain != nil {
   715  			errorf("repeated toolchain statement")
   716  			return
   717  		}
   718  		if len(args) != 1 {
   719  			errorf("toolchain directive expects exactly one argument")
   720  			return
   721  		} else if !ToolchainRE.MatchString(args[0]) {
   722  			errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0])
   723  			return
   724  		}
   725  
   726  		f.Toolchain = &Toolchain{Syntax: line}
   727  		f.Toolchain.Name = args[0]
   728  
   729  	case "godebug":
   730  		if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") {
   731  			errorf("usage: godebug key=value")
   732  			return
   733  		}
   734  		key, value, ok := strings.Cut(args[0], "=")
   735  		if !ok {
   736  			errorf("usage: godebug key=value")
   737  			return
   738  		}
   739  		f.Godebug = append(f.Godebug, &Godebug{
   740  			Key:    key,
   741  			Value:  value,
   742  			Syntax: line,
   743  		})
   744  
   745  	case "use":
   746  		if len(args) != 1 {
   747  			errorf("usage: %s local/dir", verb)
   748  			return
   749  		}
   750  		s, err := parseString(&args[0])
   751  		if err != nil {
   752  			errorf("invalid quoted string: %v", err)
   753  			return
   754  		}
   755  		f.Use = append(f.Use, &Use{
   756  			Path:   s,
   757  			Syntax: line,
   758  		})
   759  
   760  	case "replace":
   761  		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
   762  		if wrappederr != nil {
   763  			*errs = append(*errs, *wrappederr)
   764  			return
   765  		}
   766  		f.Replace = append(f.Replace, replace)
   767  	}
   768  }
   769  
   770  // IsDirectoryPath reports whether the given path should be interpreted as a directory path.
   771  // Just like on the go command line, relative paths starting with a '.' or '..' path component
   772  // and rooted paths are directory paths; the rest are module paths.
   773  func IsDirectoryPath(ns string) bool {
   774  	// Because go.mod files can move from one system to another,
   775  	// we check all known path syntaxes, both Unix and Windows.
   776  	return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) ||
   777  		ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) ||
   778  		strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) ||
   779  		len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
   780  }
   781  
   782  // MustQuote reports whether s must be quoted in order to appear as
   783  // a single token in a go.mod line.
   784  func MustQuote(s string) bool {
   785  	for _, r := range s {
   786  		switch r {
   787  		case ' ', '"', '\'', '`':
   788  			return true
   789  
   790  		case '(', ')', '[', ']', '{', '}', ',':
   791  			if len(s) > 1 {
   792  				return true
   793  			}
   794  
   795  		default:
   796  			if !unicode.IsPrint(r) {
   797  				return true
   798  			}
   799  		}
   800  	}
   801  	return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
   802  }
   803  
   804  // AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
   805  // the quotation of s.
   806  func AutoQuote(s string) string {
   807  	if MustQuote(s) {
   808  		return strconv.Quote(s)
   809  	}
   810  	return s
   811  }
   812  
   813  func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) {
   814  	toks := *args
   815  	if len(toks) == 0 || toks[0] == "(" {
   816  		return VersionInterval{}, fmt.Errorf("expected '[' or version")
   817  	}
   818  	if toks[0] != "[" {
   819  		v, err := parseVersion(verb, path, &toks[0], fix)
   820  		if err != nil {
   821  			return VersionInterval{}, err
   822  		}
   823  		*args = toks[1:]
   824  		return VersionInterval{Low: v, High: v}, nil
   825  	}
   826  	toks = toks[1:]
   827  
   828  	if len(toks) == 0 {
   829  		return VersionInterval{}, fmt.Errorf("expected version after '['")
   830  	}
   831  	low, err := parseVersion(verb, path, &toks[0], fix)
   832  	if err != nil {
   833  		return VersionInterval{}, err
   834  	}
   835  	toks = toks[1:]
   836  
   837  	if len(toks) == 0 || toks[0] != "," {
   838  		return VersionInterval{}, fmt.Errorf("expected ',' after version")
   839  	}
   840  	toks = toks[1:]
   841  
   842  	if len(toks) == 0 {
   843  		return VersionInterval{}, fmt.Errorf("expected version after ','")
   844  	}
   845  	high, err := parseVersion(verb, path, &toks[0], fix)
   846  	if err != nil {
   847  		return VersionInterval{}, err
   848  	}
   849  	toks = toks[1:]
   850  
   851  	if len(toks) == 0 || toks[0] != "]" {
   852  		return VersionInterval{}, fmt.Errorf("expected ']' after version")
   853  	}
   854  	toks = toks[1:]
   855  
   856  	*args = toks
   857  	return VersionInterval{Low: low, High: high}, nil
   858  }
   859  
   860  func parseString(s *string) (string, error) {
   861  	t := *s
   862  	if strings.HasPrefix(t, `"`) {
   863  		var err error
   864  		if t, err = strconv.Unquote(t); err != nil {
   865  			return "", err
   866  		}
   867  	} else if strings.ContainsAny(t, "\"'`") {
   868  		// Other quotes are reserved both for possible future expansion
   869  		// and to avoid confusion. For example if someone types 'x'
   870  		// we want that to be a syntax error and not a literal x in literal quotation marks.
   871  		return "", fmt.Errorf("unquoted string cannot contain quote")
   872  	}
   873  	*s = AutoQuote(t)
   874  	return t, nil
   875  }
   876  
   877  var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`)
   878  
   879  // parseDeprecation extracts the text of comments on a "module" directive and
   880  // extracts a deprecation message from that.
   881  //
   882  // A deprecation message is contained in a paragraph within a block of comments
   883  // that starts with "Deprecated:" (case sensitive). The message runs until the
   884  // end of the paragraph and does not include the "Deprecated:" prefix. If the
   885  // comment block has multiple paragraphs that start with "Deprecated:",
   886  // parseDeprecation returns the message from the first.
   887  func parseDeprecation(block *LineBlock, line *Line) string {
   888  	text := parseDirectiveComment(block, line)
   889  	m := deprecatedRE.FindStringSubmatch(text)
   890  	if m == nil {
   891  		return ""
   892  	}
   893  	return m[1]
   894  }
   895  
   896  // parseDirectiveComment extracts the text of comments on a directive.
   897  // If the directive's line does not have comments and is part of a block that
   898  // does have comments, the block's comments are used.
   899  func parseDirectiveComment(block *LineBlock, line *Line) string {
   900  	comments := line.Comment()
   901  	if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
   902  		comments = block.Comment()
   903  	}
   904  	groups := [][]Comment{comments.Before, comments.Suffix}
   905  	var lines []string
   906  	for _, g := range groups {
   907  		for _, c := range g {
   908  			if !strings.HasPrefix(c.Token, "//") {
   909  				continue // blank line
   910  			}
   911  			lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
   912  		}
   913  	}
   914  	return strings.Join(lines, "\n")
   915  }
   916  
   917  type ErrorList []Error
   918  
   919  func (e ErrorList) Error() string {
   920  	errStrs := make([]string, len(e))
   921  	for i, err := range e {
   922  		errStrs[i] = err.Error()
   923  	}
   924  	return strings.Join(errStrs, "\n")
   925  }
   926  
   927  type Error struct {
   928  	Filename string
   929  	Pos      Position
   930  	Verb     string
   931  	ModPath  string
   932  	Err      error
   933  }
   934  
   935  func (e *Error) Error() string {
   936  	var pos string
   937  	if e.Pos.LineRune > 1 {
   938  		// Don't print LineRune if it's 1 (beginning of line).
   939  		// It's always 1 except in scanner errors, which are rare.
   940  		pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune)
   941  	} else if e.Pos.Line > 0 {
   942  		pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line)
   943  	} else if e.Filename != "" {
   944  		pos = fmt.Sprintf("%s: ", e.Filename)
   945  	}
   946  
   947  	var directive string
   948  	if e.ModPath != "" {
   949  		directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
   950  	} else if e.Verb != "" {
   951  		directive = fmt.Sprintf("%s: ", e.Verb)
   952  	}
   953  
   954  	return pos + directive + e.Err.Error()
   955  }
   956  
   957  func (e *Error) Unwrap() error { return e.Err }
   958  
   959  func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
   960  	t, err := parseString(s)
   961  	if err != nil {
   962  		return "", &Error{
   963  			Verb:    verb,
   964  			ModPath: path,
   965  			Err: &module.InvalidVersionError{
   966  				Version: *s,
   967  				Err:     err,
   968  			},
   969  		}
   970  	}
   971  	if fix != nil {
   972  		fixed, err := fix(path, t)
   973  		if err != nil {
   974  			if err, ok := err.(*module.ModuleError); ok {
   975  				return "", &Error{
   976  					Verb:    verb,
   977  					ModPath: path,
   978  					Err:     err.Err,
   979  				}
   980  			}
   981  			return "", err
   982  		}
   983  		t = fixed
   984  	} else {
   985  		cv := module.CanonicalVersion(t)
   986  		if cv == "" {
   987  			return "", &Error{
   988  				Verb:    verb,
   989  				ModPath: path,
   990  				Err: &module.InvalidVersionError{
   991  					Version: t,
   992  					Err:     errors.New("must be of the form v1.2.3"),
   993  				},
   994  			}
   995  		}
   996  		t = cv
   997  	}
   998  	*s = t
   999  	return *s, nil
  1000  }
  1001  
  1002  func modulePathMajor(path string) (string, error) {
  1003  	_, major, ok := module.SplitPathVersion(path)
  1004  	if !ok {
  1005  		return "", fmt.Errorf("invalid module path")
  1006  	}
  1007  	return major, nil
  1008  }
  1009  
  1010  func (f *File) Format() ([]byte, error) {
  1011  	return Format(f.Syntax), nil
  1012  }
  1013  
  1014  // Cleanup cleans up the file f after any edit operations.
  1015  // To avoid quadratic behavior, modifications like [File.DropRequire]
  1016  // clear the entry but do not remove it from the slice.
  1017  // Cleanup cleans out all the cleared entries.
  1018  func (f *File) Cleanup() {
  1019  	w := 0
  1020  	for _, g := range f.Godebug {
  1021  		if g.Key != "" {
  1022  			f.Godebug[w] = g
  1023  			w++
  1024  		}
  1025  	}
  1026  	f.Godebug = f.Godebug[:w]
  1027  
  1028  	w = 0
  1029  	for _, r := range f.Require {
  1030  		if r.Mod.Path != "" {
  1031  			f.Require[w] = r
  1032  			w++
  1033  		}
  1034  	}
  1035  	f.Require = f.Require[:w]
  1036  
  1037  	w = 0
  1038  	for _, x := range f.Exclude {
  1039  		if x.Mod.Path != "" {
  1040  			f.Exclude[w] = x
  1041  			w++
  1042  		}
  1043  	}
  1044  	f.Exclude = f.Exclude[:w]
  1045  
  1046  	w = 0
  1047  	for _, r := range f.Replace {
  1048  		if r.Old.Path != "" {
  1049  			f.Replace[w] = r
  1050  			w++
  1051  		}
  1052  	}
  1053  	f.Replace = f.Replace[:w]
  1054  
  1055  	w = 0
  1056  	for _, r := range f.Retract {
  1057  		if r.Low != "" || r.High != "" {
  1058  			f.Retract[w] = r
  1059  			w++
  1060  		}
  1061  	}
  1062  	f.Retract = f.Retract[:w]
  1063  
  1064  	f.Syntax.Cleanup()
  1065  }
  1066  
  1067  func (f *File) AddGoStmt(version string) error {
  1068  	if !GoVersionRE.MatchString(version) {
  1069  		return fmt.Errorf("invalid language version %q", version)
  1070  	}
  1071  	if f.Go == nil {
  1072  		var hint Expr
  1073  		if f.Module != nil && f.Module.Syntax != nil {
  1074  			hint = f.Module.Syntax
  1075  		} else if f.Syntax == nil {
  1076  			f.Syntax = new(FileSyntax)
  1077  		}
  1078  		f.Go = &Go{
  1079  			Version: version,
  1080  			Syntax:  f.Syntax.addLine(hint, "go", version),
  1081  		}
  1082  	} else {
  1083  		f.Go.Version = version
  1084  		f.Syntax.updateLine(f.Go.Syntax, "go", version)
  1085  	}
  1086  	return nil
  1087  }
  1088  
  1089  // DropGoStmt deletes the go statement from the file.
  1090  func (f *File) DropGoStmt() {
  1091  	if f.Go != nil {
  1092  		f.Go.Syntax.markRemoved()
  1093  		f.Go = nil
  1094  	}
  1095  }
  1096  
  1097  // DropToolchainStmt deletes the toolchain statement from the file.
  1098  func (f *File) DropToolchainStmt() {
  1099  	if f.Toolchain != nil {
  1100  		f.Toolchain.Syntax.markRemoved()
  1101  		f.Toolchain = nil
  1102  	}
  1103  }
  1104  
  1105  func (f *File) AddToolchainStmt(name string) error {
  1106  	if !ToolchainRE.MatchString(name) {
  1107  		return fmt.Errorf("invalid toolchain name %q", name)
  1108  	}
  1109  	if f.Toolchain == nil {
  1110  		var hint Expr
  1111  		if f.Go != nil && f.Go.Syntax != nil {
  1112  			hint = f.Go.Syntax
  1113  		} else if f.Module != nil && f.Module.Syntax != nil {
  1114  			hint = f.Module.Syntax
  1115  		}
  1116  		f.Toolchain = &Toolchain{
  1117  			Name:   name,
  1118  			Syntax: f.Syntax.addLine(hint, "toolchain", name),
  1119  		}
  1120  	} else {
  1121  		f.Toolchain.Name = name
  1122  		f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
  1123  	}
  1124  	return nil
  1125  }
  1126  
  1127  // AddGodebug sets the first godebug line for key to value,
  1128  // preserving any existing comments for that line and removing all
  1129  // other godebug lines for key.
  1130  //
  1131  // If no line currently exists for key, AddGodebug adds a new line
  1132  // at the end of the last godebug block.
  1133  func (f *File) AddGodebug(key, value string) error {
  1134  	need := true
  1135  	for _, g := range f.Godebug {
  1136  		if g.Key == key {
  1137  			if need {
  1138  				g.Value = value
  1139  				f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value)
  1140  				need = false
  1141  			} else {
  1142  				g.Syntax.markRemoved()
  1143  				*g = Godebug{}
  1144  			}
  1145  		}
  1146  	}
  1147  
  1148  	if need {
  1149  		f.addNewGodebug(key, value)
  1150  	}
  1151  	return nil
  1152  }
  1153  
  1154  // addNewGodebug adds a new godebug key=value line at the end
  1155  // of the last godebug block, regardless of any existing godebug lines for key.
  1156  func (f *File) addNewGodebug(key, value string) {
  1157  	line := f.Syntax.addLine(nil, "godebug", key+"="+value)
  1158  	g := &Godebug{
  1159  		Key:    key,
  1160  		Value:  value,
  1161  		Syntax: line,
  1162  	}
  1163  	f.Godebug = append(f.Godebug, g)
  1164  }
  1165  
  1166  // AddRequire sets the first require line for path to version vers,
  1167  // preserving any existing comments for that line and removing all
  1168  // other lines for path.
  1169  //
  1170  // If no line currently exists for path, AddRequire adds a new line
  1171  // at the end of the last require block.
  1172  func (f *File) AddRequire(path, vers string) error {
  1173  	need := true
  1174  	for _, r := range f.Require {
  1175  		if r.Mod.Path == path {
  1176  			if need {
  1177  				r.Mod.Version = vers
  1178  				f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
  1179  				need = false
  1180  			} else {
  1181  				r.Syntax.markRemoved()
  1182  				*r = Require{}
  1183  			}
  1184  		}
  1185  	}
  1186  
  1187  	if need {
  1188  		f.AddNewRequire(path, vers, false)
  1189  	}
  1190  	return nil
  1191  }
  1192  
  1193  // AddNewRequire adds a new require line for path at version vers at the end of
  1194  // the last require block, regardless of any existing require lines for path.
  1195  func (f *File) AddNewRequire(path, vers string, indirect bool) {
  1196  	line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
  1197  	r := &Require{
  1198  		Mod:    module.Version{Path: path, Version: vers},
  1199  		Syntax: line,
  1200  	}
  1201  	r.setIndirect(indirect)
  1202  	f.Require = append(f.Require, r)
  1203  }
  1204  
  1205  // SetRequire updates the requirements of f to contain exactly req, preserving
  1206  // the existing block structure and line comment contents (except for 'indirect'
  1207  // markings) for the first requirement on each named module path.
  1208  //
  1209  // The Syntax field is ignored for the requirements in req.
  1210  //
  1211  // Any requirements not already present in the file are added to the block
  1212  // containing the last require line.
  1213  //
  1214  // The requirements in req must specify at most one distinct version for each
  1215  // module path.
  1216  //
  1217  // If any existing requirements may be removed, the caller should call
  1218  // [File.Cleanup] after all edits are complete.
  1219  func (f *File) SetRequire(req []*Require) {
  1220  	type elem struct {
  1221  		version  string
  1222  		indirect bool
  1223  	}
  1224  	need := make(map[string]elem)
  1225  	for _, r := range req {
  1226  		if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version {
  1227  			panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version))
  1228  		}
  1229  		need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect}
  1230  	}
  1231  
  1232  	// Update or delete the existing Require entries to preserve
  1233  	// only the first for each module path in req.
  1234  	for _, r := range f.Require {
  1235  		e, ok := need[r.Mod.Path]
  1236  		if ok {
  1237  			r.setVersion(e.version)
  1238  			r.setIndirect(e.indirect)
  1239  		} else {
  1240  			r.markRemoved()
  1241  		}
  1242  		delete(need, r.Mod.Path)
  1243  	}
  1244  
  1245  	// Add new entries in the last block of the file for any paths that weren't
  1246  	// already present.
  1247  	//
  1248  	// This step is nondeterministic, but the final result will be deterministic
  1249  	// because we will sort the block.
  1250  	for path, e := range need {
  1251  		f.AddNewRequire(path, e.version, e.indirect)
  1252  	}
  1253  
  1254  	f.SortBlocks()
  1255  }
  1256  
  1257  // SetRequireSeparateIndirect updates the requirements of f to contain the given
  1258  // requirements. Comment contents (except for 'indirect' markings) are retained
  1259  // from the first existing requirement for each module path. Like SetRequire,
  1260  // SetRequireSeparateIndirect adds requirements for new paths in req,
  1261  // updates the version and "// indirect" comment on existing requirements,
  1262  // and deletes requirements on paths not in req. Existing duplicate requirements
  1263  // are deleted.
  1264  //
  1265  // As its name suggests, SetRequireSeparateIndirect puts direct and indirect
  1266  // requirements into two separate blocks, one containing only direct
  1267  // requirements, and the other containing only indirect requirements.
  1268  // SetRequireSeparateIndirect may move requirements between these two blocks
  1269  // when their indirect markings change. However, SetRequireSeparateIndirect
  1270  // won't move requirements from other blocks, especially blocks with comments.
  1271  //
  1272  // If the file initially has one uncommented block of requirements,
  1273  // SetRequireSeparateIndirect will split it into a direct-only and indirect-only
  1274  // block. This aids in the transition to separate blocks.
  1275  func (f *File) SetRequireSeparateIndirect(req []*Require) {
  1276  	f.setRequireSeparateIndirect(req, false)
  1277  }
  1278  
  1279  // SetRequireAtMostTwo is like SetRequireSeparateIndirect but it aggressively
  1280  // consolidates all requirements into at most two blocks (one direct, one indirect).
  1281  // It ignores existing blocks and comments when deciding where to place requirements.
  1282  func (f *File) SetRequireAtMostTwo(req []*Require) {
  1283  	f.setRequireSeparateIndirect(req, true)
  1284  }
  1285  
  1286  func (f *File) setRequireSeparateIndirect(req []*Require, simplify bool) {
  1287  	// hasComments returns whether a line or block has comments
  1288  	// other than "indirect".
  1289  	hasComments := func(c Comments) bool {
  1290  		return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 ||
  1291  			(len(c.Suffix) == 1 &&
  1292  				strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect")
  1293  	}
  1294  
  1295  	// moveReq adds r to block. If r was in another block, moveReq deletes
  1296  	// it from that block and transfers its comments.
  1297  	moveReq := func(r *Require, block *LineBlock) {
  1298  		var line *Line
  1299  		if r.Syntax == nil {
  1300  			line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}}
  1301  			r.Syntax = line
  1302  			if r.Indirect {
  1303  				r.setIndirect(true)
  1304  			}
  1305  		} else {
  1306  			line = new(Line)
  1307  			*line = *r.Syntax
  1308  			if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" {
  1309  				line.Token = line.Token[1:]
  1310  			}
  1311  			r.Syntax.Token = nil // Cleanup will delete the old line.
  1312  			r.Syntax = line
  1313  		}
  1314  		line.InBlock = true
  1315  		block.Line = append(block.Line, line)
  1316  	}
  1317  
  1318  	// Examine existing require lines and blocks.
  1319  	need := make(map[string]*Require)
  1320  	for _, r := range req {
  1321  		need[r.Mod.Path] = r
  1322  	}
  1323  	lineIndirect := make(map[*Line]bool)
  1324  	for _, r := range f.Require {
  1325  		if n := need[r.Mod.Path]; n != nil {
  1326  			lineIndirect[r.Syntax] = n.Indirect
  1327  		}
  1328  	}
  1329  
  1330  	var (
  1331  		// We may insert new requirements into the last uncommented
  1332  		// direct-only and indirect-only blocks. We may also move requirements
  1333  		// to the opposite block if their indirect markings change.
  1334  		lastDirectIndex   = -1
  1335  		lastIndirectIndex = -1
  1336  
  1337  		// If there are no direct-only or indirect-only blocks, a new block may
  1338  		// be inserted after the last require line or block.
  1339  		lastRequireIndex = -1
  1340  
  1341  		// If there's only one require line or block, and it's uncommented,
  1342  		// we'll move its requirements to the direct-only or indirect-only blocks.
  1343  		requireLineOrBlockCount = 0
  1344  
  1345  		// Track the block each requirement belongs to (if any) so we can
  1346  		// move them later.
  1347  		lineToBlock           = make(map[*Line]*LineBlock)
  1348  		directBlockComments   []Comment
  1349  		indirectBlockComments []Comment
  1350  	)
  1351  	for i, stmt := range f.Syntax.Stmt {
  1352  		switch stmt := stmt.(type) {
  1353  		case *Line:
  1354  			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
  1355  				continue
  1356  			}
  1357  			lastRequireIndex = i
  1358  			requireLineOrBlockCount++
  1359  			if !hasComments(stmt.Comments) {
  1360  				if isIndirect(stmt) {
  1361  					lastIndirectIndex = i
  1362  				} else {
  1363  					lastDirectIndex = i
  1364  				}
  1365  			}
  1366  
  1367  		case *LineBlock:
  1368  			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
  1369  				continue
  1370  			}
  1371  			lastRequireIndex = i
  1372  			requireLineOrBlockCount++
  1373  			allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
  1374  			allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
  1375  			for _, line := range stmt.Line {
  1376  				lineToBlock[line] = stmt
  1377  				if hasComments(line.Comments) {
  1378  					allDirect = false
  1379  					allIndirect = false
  1380  				} else if isIndirect(line) {
  1381  					allDirect = false
  1382  				} else {
  1383  					allIndirect = false
  1384  				}
  1385  			}
  1386  			if allDirect {
  1387  				lastDirectIndex = i
  1388  			}
  1389  			if allIndirect {
  1390  				lastIndirectIndex = i
  1391  			}
  1392  			if simplify {
  1393  				anyDirect := false
  1394  				for _, line := range stmt.Line {
  1395  					if ind, ok := lineIndirect[line]; ok && !ind {
  1396  						anyDirect = true
  1397  						break
  1398  					}
  1399  				}
  1400  				target := &directBlockComments
  1401  				if !anyDirect && len(stmt.Line) > 0 {
  1402  					target = &indirectBlockComments
  1403  				}
  1404  				if len(*target) > 0 && len(stmt.Comments.Before) > 0 {
  1405  					*target = append(*target, Comment{Token: "//"})
  1406  				}
  1407  				*target = append(*target, stmt.Comments.Before...)
  1408  				stmt.Comments.Before = nil
  1409  			}
  1410  		}
  1411  	}
  1412  
  1413  	oneFlatUncommentedBlock := requireLineOrBlockCount == 1 &&
  1414  		!hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment())
  1415  
  1416  	// Create direct and indirect blocks if needed. Convert lines into blocks
  1417  	// if needed. If we end up with an empty block or a one-line block,
  1418  	// Cleanup will delete it or convert it to a line later.
  1419  	insertBlock := func(i int) *LineBlock {
  1420  		block := &LineBlock{Token: []string{"require"}}
  1421  		f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
  1422  		copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
  1423  		f.Syntax.Stmt[i] = block
  1424  		return block
  1425  	}
  1426  
  1427  	ensureBlock := func(i int) *LineBlock {
  1428  		switch stmt := f.Syntax.Stmt[i].(type) {
  1429  		case *LineBlock:
  1430  			return stmt
  1431  		case *Line:
  1432  			block := &LineBlock{
  1433  				Token: []string{"require"},
  1434  				Line:  []*Line{stmt},
  1435  			}
  1436  			stmt.Token = stmt.Token[1:] // remove "require"
  1437  			stmt.InBlock = true
  1438  			f.Syntax.Stmt[i] = block
  1439  			return block
  1440  		default:
  1441  			panic(fmt.Sprintf("unexpected statement: %v", stmt))
  1442  		}
  1443  	}
  1444  
  1445  	var lastDirectBlock *LineBlock
  1446  	if lastDirectIndex < 0 {
  1447  		if lastIndirectIndex >= 0 {
  1448  			lastDirectIndex = lastIndirectIndex
  1449  			lastIndirectIndex++
  1450  		} else if lastRequireIndex >= 0 {
  1451  			lastDirectIndex = lastRequireIndex + 1
  1452  		} else {
  1453  			lastDirectIndex = len(f.Syntax.Stmt)
  1454  		}
  1455  		lastDirectBlock = insertBlock(lastDirectIndex)
  1456  	} else {
  1457  		lastDirectBlock = ensureBlock(lastDirectIndex)
  1458  	}
  1459  
  1460  	var lastIndirectBlock *LineBlock
  1461  	if lastIndirectIndex < 0 {
  1462  		lastIndirectIndex = lastDirectIndex + 1
  1463  		lastIndirectBlock = insertBlock(lastIndirectIndex)
  1464  	} else {
  1465  		lastIndirectBlock = ensureBlock(lastIndirectIndex)
  1466  	}
  1467  
  1468  	if simplify {
  1469  		if len(directBlockComments) > 0 {
  1470  			lastDirectBlock.Comments.Before = append(lastDirectBlock.Comments.Before, directBlockComments...)
  1471  		}
  1472  		if len(indirectBlockComments) > 0 {
  1473  			lastIndirectBlock.Comments.Before = append(lastIndirectBlock.Comments.Before, indirectBlockComments...)
  1474  		}
  1475  	}
  1476  
  1477  	// Delete requirements we don't want anymore.
  1478  	// Update versions and indirect comments on requirements we want to keep.
  1479  	// If a requirement is in last{Direct,Indirect}Block with the wrong
  1480  	// indirect marking after this, or if the requirement is in a single
  1481  	// uncommented mixed block (oneFlatUncommentedBlock), move it to the
  1482  	// correct block.
  1483  	//
  1484  	// Some blocks may be empty after this. Cleanup will remove them.
  1485  	have := make(map[string]*Require)
  1486  	for _, r := range f.Require {
  1487  		path := r.Mod.Path
  1488  		if need[path] == nil || have[path] != nil {
  1489  			// Requirement not needed, or duplicate requirement. Delete.
  1490  			r.markRemoved()
  1491  			continue
  1492  		}
  1493  		have[r.Mod.Path] = r
  1494  		r.setVersion(need[path].Mod.Version)
  1495  		r.setIndirect(need[path].Indirect)
  1496  		if need[path].Indirect &&
  1497  			(simplify || oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
  1498  			moveReq(r, lastIndirectBlock)
  1499  		} else if !need[path].Indirect &&
  1500  			(simplify || oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
  1501  			moveReq(r, lastDirectBlock)
  1502  		}
  1503  	}
  1504  
  1505  	// Add new requirements.
  1506  	for path, r := range need {
  1507  		if have[path] == nil {
  1508  			if r.Indirect {
  1509  				moveReq(r, lastIndirectBlock)
  1510  			} else {
  1511  				moveReq(r, lastDirectBlock)
  1512  			}
  1513  			f.Require = append(f.Require, r)
  1514  		}
  1515  	}
  1516  
  1517  	f.SortBlocks()
  1518  }
  1519  
  1520  func (f *File) DropGodebug(key string) error {
  1521  	for _, g := range f.Godebug {
  1522  		if g.Key == key {
  1523  			g.Syntax.markRemoved()
  1524  			*g = Godebug{}
  1525  		}
  1526  	}
  1527  	return nil
  1528  }
  1529  
  1530  func (f *File) DropRequire(path string) error {
  1531  	for _, r := range f.Require {
  1532  		if r.Mod.Path == path {
  1533  			r.Syntax.markRemoved()
  1534  			*r = Require{}
  1535  		}
  1536  	}
  1537  	return nil
  1538  }
  1539  
  1540  // AddExclude adds an exclude statement to the mod file. Errors if the provided
  1541  // version is not a canonical version string
  1542  func (f *File) AddExclude(path, vers string) error {
  1543  	if err := checkCanonicalVersion(path, vers); err != nil {
  1544  		return err
  1545  	}
  1546  
  1547  	var hint *Line
  1548  	for _, x := range f.Exclude {
  1549  		if x.Mod.Path == path && x.Mod.Version == vers {
  1550  			return nil
  1551  		}
  1552  		if x.Mod.Path == path {
  1553  			hint = x.Syntax
  1554  		}
  1555  	}
  1556  
  1557  	f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
  1558  	return nil
  1559  }
  1560  
  1561  func (f *File) DropExclude(path, vers string) error {
  1562  	for _, x := range f.Exclude {
  1563  		if x.Mod.Path == path && x.Mod.Version == vers {
  1564  			x.Syntax.markRemoved()
  1565  			*x = Exclude{}
  1566  		}
  1567  	}
  1568  	return nil
  1569  }
  1570  
  1571  func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
  1572  	return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
  1573  }
  1574  
  1575  func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
  1576  	need := true
  1577  	old := module.Version{Path: oldPath, Version: oldVers}
  1578  	new := module.Version{Path: newPath, Version: newVers}
  1579  	tokens := []string{"replace", AutoQuote(oldPath)}
  1580  	if oldVers != "" {
  1581  		tokens = append(tokens, oldVers)
  1582  	}
  1583  	tokens = append(tokens, "=>", AutoQuote(newPath))
  1584  	if newVers != "" {
  1585  		tokens = append(tokens, newVers)
  1586  	}
  1587  
  1588  	var hint *Line
  1589  	for _, r := range *replace {
  1590  		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
  1591  			if need {
  1592  				// Found replacement for old; update to use new.
  1593  				r.New = new
  1594  				syntax.updateLine(r.Syntax, tokens...)
  1595  				need = false
  1596  				continue
  1597  			}
  1598  			// Already added; delete other replacements for same.
  1599  			r.Syntax.markRemoved()
  1600  			*r = Replace{}
  1601  		}
  1602  		if r.Old.Path == oldPath {
  1603  			hint = r.Syntax
  1604  		}
  1605  	}
  1606  	if need {
  1607  		*replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
  1608  	}
  1609  	return nil
  1610  }
  1611  
  1612  func (f *File) DropReplace(oldPath, oldVers string) error {
  1613  	for _, r := range f.Replace {
  1614  		if r.Old.Path == oldPath && r.Old.Version == oldVers {
  1615  			r.Syntax.markRemoved()
  1616  			*r = Replace{}
  1617  		}
  1618  	}
  1619  	return nil
  1620  }
  1621  
  1622  // AddRetract adds a retract statement to the mod file. Errors if the provided
  1623  // version interval does not consist of canonical version strings
  1624  func (f *File) AddRetract(vi VersionInterval, rationale string) error {
  1625  	var path string
  1626  	if f.Module != nil {
  1627  		path = f.Module.Mod.Path
  1628  	}
  1629  	if err := checkCanonicalVersion(path, vi.High); err != nil {
  1630  		return err
  1631  	}
  1632  	if err := checkCanonicalVersion(path, vi.Low); err != nil {
  1633  		return err
  1634  	}
  1635  
  1636  	r := &Retract{
  1637  		VersionInterval: vi,
  1638  	}
  1639  	if vi.Low == vi.High {
  1640  		r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
  1641  	} else {
  1642  		r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
  1643  	}
  1644  	if rationale != "" {
  1645  		for line := range strings.SplitSeq(rationale, "\n") {
  1646  			com := Comment{Token: "// " + line}
  1647  			r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
  1648  		}
  1649  	}
  1650  	return nil
  1651  }
  1652  
  1653  func (f *File) DropRetract(vi VersionInterval) error {
  1654  	for _, r := range f.Retract {
  1655  		if r.VersionInterval == vi {
  1656  			r.Syntax.markRemoved()
  1657  			*r = Retract{}
  1658  		}
  1659  	}
  1660  	return nil
  1661  }
  1662  
  1663  // AddTool adds a new tool directive with the given path.
  1664  // It does nothing if the tool line already exists.
  1665  func (f *File) AddTool(path string) error {
  1666  	for _, t := range f.Tool {
  1667  		if t.Path == path {
  1668  			return nil
  1669  		}
  1670  	}
  1671  
  1672  	f.Tool = append(f.Tool, &Tool{
  1673  		Path:   path,
  1674  		Syntax: f.Syntax.addLine(nil, "tool", path),
  1675  	})
  1676  
  1677  	f.SortBlocks()
  1678  	return nil
  1679  }
  1680  
  1681  // DropTool removes a tool directive with the given path.
  1682  // It does nothing if no such tool directive exists.
  1683  func (f *File) DropTool(path string) error {
  1684  	for _, t := range f.Tool {
  1685  		if t.Path == path {
  1686  			t.Syntax.markRemoved()
  1687  			*t = Tool{}
  1688  		}
  1689  	}
  1690  	return nil
  1691  }
  1692  
  1693  // AddIgnore adds a new ignore directive with the given path.
  1694  // It does nothing if the ignore line already exists.
  1695  func (f *File) AddIgnore(path string) error {
  1696  	for _, t := range f.Ignore {
  1697  		if t.Path == path {
  1698  			return nil
  1699  		}
  1700  	}
  1701  
  1702  	f.Ignore = append(f.Ignore, &Ignore{
  1703  		Path:   path,
  1704  		Syntax: f.Syntax.addLine(nil, "ignore", path),
  1705  	})
  1706  
  1707  	f.SortBlocks()
  1708  	return nil
  1709  }
  1710  
  1711  // DropIgnore removes an ignore directive with the given path.
  1712  // It does nothing if no such ignore directive exists.
  1713  func (f *File) DropIgnore(path string) error {
  1714  	for _, t := range f.Ignore {
  1715  		if t.Path == path {
  1716  			t.Syntax.markRemoved()
  1717  			*t = Ignore{}
  1718  		}
  1719  	}
  1720  	return nil
  1721  }
  1722  
  1723  func (f *File) SortBlocks() {
  1724  	f.removeDups() // otherwise sorting is unsafe
  1725  
  1726  	// semanticSortForExcludeVersionV is the Go version (plus leading "v") at which
  1727  	// lines in exclude blocks start to use semantic sort instead of lexicographic sort.
  1728  	// See go.dev/issue/60028.
  1729  	const semanticSortForExcludeVersionV = "v1.21"
  1730  	useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0
  1731  
  1732  	for _, stmt := range f.Syntax.Stmt {
  1733  		block, ok := stmt.(*LineBlock)
  1734  		if !ok {
  1735  			continue
  1736  		}
  1737  		less := compareLine
  1738  		if block.Token[0] == "exclude" && useSemanticSortForExclude {
  1739  			less = compareLineExclude
  1740  		} else if block.Token[0] == "retract" {
  1741  			less = compareLineRetract
  1742  		}
  1743  		slices.SortStableFunc(block.Line, less)
  1744  	}
  1745  }
  1746  
  1747  // removeDups removes duplicate exclude, replace and tool directives.
  1748  //
  1749  // Earlier exclude and tool directives take priority.
  1750  //
  1751  // Later replace directives take priority.
  1752  //
  1753  // require directives are not de-duplicated. That's left up to higher-level
  1754  // logic (MVS).
  1755  //
  1756  // retract directives are not de-duplicated since comments are
  1757  // meaningful, and versions may be retracted multiple times.
  1758  func (f *File) removeDups() {
  1759  	removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool, &f.Ignore)
  1760  }
  1761  
  1762  func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool, ignore *[]*Ignore) {
  1763  	kill := make(map[*Line]bool)
  1764  
  1765  	// Remove duplicate excludes.
  1766  	if exclude != nil {
  1767  		haveExclude := make(map[module.Version]bool)
  1768  		for _, x := range *exclude {
  1769  			if haveExclude[x.Mod] {
  1770  				kill[x.Syntax] = true
  1771  				continue
  1772  			}
  1773  			haveExclude[x.Mod] = true
  1774  		}
  1775  		var excl []*Exclude
  1776  		for _, x := range *exclude {
  1777  			if !kill[x.Syntax] {
  1778  				excl = append(excl, x)
  1779  			}
  1780  		}
  1781  		*exclude = excl
  1782  	}
  1783  
  1784  	// Remove duplicate replacements.
  1785  	// Later replacements take priority over earlier ones.
  1786  	haveReplace := make(map[module.Version]bool)
  1787  	for _, x := range slices.Backward(*replace) {
  1788  		if haveReplace[x.Old] {
  1789  			kill[x.Syntax] = true
  1790  			continue
  1791  		}
  1792  		haveReplace[x.Old] = true
  1793  	}
  1794  	var repl []*Replace
  1795  	for _, x := range *replace {
  1796  		if !kill[x.Syntax] {
  1797  			repl = append(repl, x)
  1798  		}
  1799  	}
  1800  	*replace = repl
  1801  
  1802  	if tool != nil {
  1803  		haveTool := make(map[string]bool)
  1804  		for _, t := range *tool {
  1805  			if haveTool[t.Path] {
  1806  				kill[t.Syntax] = true
  1807  				continue
  1808  			}
  1809  			haveTool[t.Path] = true
  1810  		}
  1811  		var newTool []*Tool
  1812  		for _, t := range *tool {
  1813  			if !kill[t.Syntax] {
  1814  				newTool = append(newTool, t)
  1815  			}
  1816  		}
  1817  		*tool = newTool
  1818  	}
  1819  
  1820  	if ignore != nil {
  1821  		haveIgnore := make(map[string]bool)
  1822  		for _, i := range *ignore {
  1823  			if haveIgnore[i.Path] {
  1824  				kill[i.Syntax] = true
  1825  				continue
  1826  			}
  1827  			haveIgnore[i.Path] = true
  1828  		}
  1829  		var newIgnore []*Ignore
  1830  		for _, i := range *ignore {
  1831  			if !kill[i.Syntax] {
  1832  				newIgnore = append(newIgnore, i)
  1833  			}
  1834  		}
  1835  		*ignore = newIgnore
  1836  	}
  1837  
  1838  	// Duplicate require and retract directives are not removed.
  1839  
  1840  	// Drop killed statements from the syntax tree.
  1841  	var stmts []Expr
  1842  	for _, stmt := range syntax.Stmt {
  1843  		switch stmt := stmt.(type) {
  1844  		case *Line:
  1845  			if kill[stmt] {
  1846  				continue
  1847  			}
  1848  		case *LineBlock:
  1849  			var lines []*Line
  1850  			for _, line := range stmt.Line {
  1851  				if !kill[line] {
  1852  					lines = append(lines, line)
  1853  				}
  1854  			}
  1855  			stmt.Line = lines
  1856  			if len(lines) == 0 {
  1857  				continue
  1858  			}
  1859  		}
  1860  		stmts = append(stmts, stmt)
  1861  	}
  1862  	syntax.Stmt = stmts
  1863  }
  1864  
  1865  // compareLine compares li and lj. It sorts lexicographically without assigning
  1866  // any special meaning to tokens.
  1867  func compareLine(li, lj *Line) int {
  1868  	for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
  1869  		if li.Token[k] != lj.Token[k] {
  1870  			return cmp.Compare(li.Token[k], lj.Token[k])
  1871  		}
  1872  	}
  1873  	return cmp.Compare(len(li.Token), len(lj.Token))
  1874  }
  1875  
  1876  // compareLineExclude compares li and lj for lines in an "exclude" block.
  1877  func compareLineExclude(li, lj *Line) int {
  1878  	if len(li.Token) != 2 || len(lj.Token) != 2 {
  1879  		// Not a known exclude specification.
  1880  		// Fall back to sorting lexicographically.
  1881  		return compareLine(li, lj)
  1882  	}
  1883  	// An exclude specification has two tokens: ModulePath and Version.
  1884  	// Compare module path by string order and version by semver rules.
  1885  	if pi, pj := li.Token[0], lj.Token[0]; pi != pj {
  1886  		return cmp.Compare(pi, pj)
  1887  	}
  1888  	return semver.Compare(li.Token[1], lj.Token[1])
  1889  }
  1890  
  1891  // compareLineRetract compares li and lj for lines in a "retract" block.
  1892  // It treats each line as a version interval. Single versions are compared as
  1893  // if they were intervals with the same low and high version.
  1894  // Intervals are sorted in descending order, first by low version, then by
  1895  // high version, using [semver.Compare].
  1896  func compareLineRetract(li, lj *Line) int {
  1897  	interval := func(l *Line) VersionInterval {
  1898  		if len(l.Token) == 1 {
  1899  			return VersionInterval{Low: l.Token[0], High: l.Token[0]}
  1900  		} else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
  1901  			return VersionInterval{Low: l.Token[1], High: l.Token[3]}
  1902  		} else {
  1903  			// Line in unknown format. Treat as an invalid version.
  1904  			return VersionInterval{}
  1905  		}
  1906  	}
  1907  	vii := interval(li)
  1908  	vij := interval(lj)
  1909  	if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
  1910  		return -cmp
  1911  	}
  1912  	return -semver.Compare(vii.High, vij.High)
  1913  }
  1914  
  1915  // checkCanonicalVersion returns a non-nil error if vers is not a canonical
  1916  // version string or does not match the major version of path.
  1917  //
  1918  // If path is non-empty, the error text suggests a format with a major version
  1919  // corresponding to the path.
  1920  func checkCanonicalVersion(path, vers string) error {
  1921  	_, pathMajor, pathMajorOk := module.SplitPathVersion(path)
  1922  
  1923  	if vers == "" || vers != module.CanonicalVersion(vers) {
  1924  		if pathMajor == "" {
  1925  			return &module.InvalidVersionError{
  1926  				Version: vers,
  1927  				Err:     fmt.Errorf("must be of the form v1.2.3"),
  1928  			}
  1929  		}
  1930  		return &module.InvalidVersionError{
  1931  			Version: vers,
  1932  			Err:     fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)),
  1933  		}
  1934  	}
  1935  
  1936  	if pathMajorOk {
  1937  		if err := module.CheckPathMajor(vers, pathMajor); err != nil {
  1938  			if pathMajor == "" {
  1939  				// In this context, the user probably wrote "v2.3.4" when they meant
  1940  				// "v2.3.4+incompatible". Suggest that instead of "v0 or v1".
  1941  				return &module.InvalidVersionError{
  1942  					Version: vers,
  1943  					Err:     fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)),
  1944  				}
  1945  			}
  1946  			return err
  1947  		}
  1948  	}
  1949  
  1950  	return nil
  1951  }
  1952  

View as plain text