...

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

Documentation: cmd/vendor/golang.org/x/mod/modfile

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

View as plain text