...

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

View as plain text