...

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

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

View as plain text