github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/internal/modfile/rule.go (about)

     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
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"path/filepath"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"unicode"
    17  
    18  	"cmd/go/internal/module"
    19  	"cmd/go/internal/semver"
    20  )
    21  
    22  // A File is the parsed, interpreted form of a go.mod file.
    23  type File struct {
    24  	Module  *Module
    25  	Go      *Go
    26  	Require []*Require
    27  	Exclude []*Exclude
    28  	Replace []*Replace
    29  
    30  	Syntax *FileSyntax
    31  }
    32  
    33  // A Module is the module statement.
    34  type Module struct {
    35  	Mod    module.Version
    36  	Syntax *Line
    37  }
    38  
    39  // A Go is the go statement.
    40  type Go struct {
    41  	Version string // "1.23"
    42  	Syntax  *Line
    43  }
    44  
    45  // A Require is a single require statement.
    46  type Require struct {
    47  	Mod      module.Version
    48  	Indirect bool // has "// indirect" comment
    49  	Syntax   *Line
    50  }
    51  
    52  // An Exclude is a single exclude statement.
    53  type Exclude struct {
    54  	Mod    module.Version
    55  	Syntax *Line
    56  }
    57  
    58  // A Replace is a single replace statement.
    59  type Replace struct {
    60  	Old    module.Version
    61  	New    module.Version
    62  	Syntax *Line
    63  }
    64  
    65  func (f *File) AddModuleStmt(path string) error {
    66  	if f.Syntax == nil {
    67  		f.Syntax = new(FileSyntax)
    68  	}
    69  	if f.Module == nil {
    70  		f.Module = &Module{
    71  			Mod:    module.Version{Path: path},
    72  			Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
    73  		}
    74  	} else {
    75  		f.Module.Mod.Path = path
    76  		f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
    77  	}
    78  	return nil
    79  }
    80  
    81  func (f *File) AddComment(text string) {
    82  	if f.Syntax == nil {
    83  		f.Syntax = new(FileSyntax)
    84  	}
    85  	f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
    86  		Comments: Comments{
    87  			Before: []Comment{
    88  				{
    89  					Token: text,
    90  				},
    91  			},
    92  		},
    93  	})
    94  }
    95  
    96  type VersionFixer func(path, version string) (string, error)
    97  
    98  // Parse parses the data, reported in errors as being from file,
    99  // into a File struct. It applies fix, if non-nil, to canonicalize all module versions found.
   100  func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
   101  	return parseToFile(file, data, fix, true)
   102  }
   103  
   104  // ParseLax is like Parse but ignores unknown statements.
   105  // It is used when parsing go.mod files other than the main module,
   106  // under the theory that most statement types we add in the future will
   107  // only apply in the main module, like exclude and replace,
   108  // and so we get better gradual deployments if old go commands
   109  // simply ignore those statements when found in go.mod files
   110  // in dependencies.
   111  func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
   112  	return parseToFile(file, data, fix, false)
   113  }
   114  
   115  func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) {
   116  	fs, err := parse(file, data)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	f := &File{
   121  		Syntax: fs,
   122  	}
   123  
   124  	var errs bytes.Buffer
   125  	for _, x := range fs.Stmt {
   126  		switch x := x.(type) {
   127  		case *Line:
   128  			f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict)
   129  
   130  		case *LineBlock:
   131  			if len(x.Token) > 1 {
   132  				if strict {
   133  					fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
   134  				}
   135  				continue
   136  			}
   137  			switch x.Token[0] {
   138  			default:
   139  				if strict {
   140  					fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
   141  				}
   142  				continue
   143  			case "module", "require", "exclude", "replace":
   144  				for _, l := range x.Line {
   145  					f.add(&errs, l, x.Token[0], l.Token, fix, strict)
   146  				}
   147  			}
   148  		}
   149  	}
   150  
   151  	if errs.Len() > 0 {
   152  		return nil, errors.New(strings.TrimRight(errs.String(), "\n"))
   153  	}
   154  	return f, nil
   155  }
   156  
   157  var GoVersionRE = regexp.MustCompile(`([1-9][0-9]*)\.(0|[1-9][0-9]*)`)
   158  
   159  func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
   160  	// If strict is false, this module is a dependency.
   161  	// We ignore all unknown directives as well as main-module-only
   162  	// directives like replace and exclude. It will work better for
   163  	// forward compatibility if we can depend on modules that have unknown
   164  	// statements (presumed relevant only when acting as the main module)
   165  	// and simply ignore those statements.
   166  	if !strict {
   167  		switch verb {
   168  		case "module", "require", "go":
   169  			// want these even for dependency go.mods
   170  		default:
   171  			return
   172  		}
   173  	}
   174  
   175  	switch verb {
   176  	default:
   177  		fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
   178  
   179  	case "go":
   180  		if f.Go != nil {
   181  			fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line)
   182  			return
   183  		}
   184  		if len(args) != 1 || !GoVersionRE.MatchString(args[0]) {
   185  			fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line)
   186  			return
   187  		}
   188  		f.Go = &Go{Syntax: line}
   189  		f.Go.Version = args[0]
   190  	case "module":
   191  		if f.Module != nil {
   192  			fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
   193  			return
   194  		}
   195  		f.Module = &Module{Syntax: line}
   196  		if len(args) != 1 {
   197  
   198  			fmt.Fprintf(errs, "%s:%d: usage: module module/path [version]\n", f.Syntax.Name, line.Start.Line)
   199  			return
   200  		}
   201  		s, err := parseString(&args[0])
   202  		if err != nil {
   203  			fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   204  			return
   205  		}
   206  		f.Module.Mod = module.Version{Path: s}
   207  	case "require", "exclude":
   208  		if len(args) != 2 {
   209  			fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3\n", f.Syntax.Name, line.Start.Line, verb)
   210  			return
   211  		}
   212  		s, err := parseString(&args[0])
   213  		if err != nil {
   214  			fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   215  			return
   216  		}
   217  		old := args[1]
   218  		v, err := parseVersion(s, &args[1], fix)
   219  		if err != nil {
   220  			fmt.Fprintf(errs, "%s:%d: invalid module version %q: %v\n", f.Syntax.Name, line.Start.Line, old, err)
   221  			return
   222  		}
   223  		pathMajor, err := modulePathMajor(s)
   224  		if err != nil {
   225  			fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   226  			return
   227  		}
   228  		if !module.MatchPathMajor(v, pathMajor) {
   229  			if pathMajor == "" {
   230  				pathMajor = "v0 or v1"
   231  			}
   232  			fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, pathMajor, semver.Major(v), v)
   233  			return
   234  		}
   235  		if verb == "require" {
   236  			f.Require = append(f.Require, &Require{
   237  				Mod:      module.Version{Path: s, Version: v},
   238  				Syntax:   line,
   239  				Indirect: isIndirect(line),
   240  			})
   241  		} else {
   242  			f.Exclude = append(f.Exclude, &Exclude{
   243  				Mod:    module.Version{Path: s, Version: v},
   244  				Syntax: line,
   245  			})
   246  		}
   247  	case "replace":
   248  		arrow := 2
   249  		if len(args) >= 2 && args[1] == "=>" {
   250  			arrow = 1
   251  		}
   252  		if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
   253  			fmt.Fprintf(errs, "%s:%d: usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory\n", f.Syntax.Name, line.Start.Line, verb, verb)
   254  			return
   255  		}
   256  		s, err := parseString(&args[0])
   257  		if err != nil {
   258  			fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   259  			return
   260  		}
   261  		pathMajor, err := modulePathMajor(s)
   262  		if err != nil {
   263  			fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   264  			return
   265  		}
   266  		var v string
   267  		if arrow == 2 {
   268  			old := args[1]
   269  			v, err = parseVersion(s, &args[1], fix)
   270  			if err != nil {
   271  				fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err)
   272  				return
   273  			}
   274  			if !module.MatchPathMajor(v, pathMajor) {
   275  				if pathMajor == "" {
   276  					pathMajor = "v0 or v1"
   277  				}
   278  				fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, pathMajor, semver.Major(v), v)
   279  				return
   280  			}
   281  		}
   282  		ns, err := parseString(&args[arrow+1])
   283  		if err != nil {
   284  			fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   285  			return
   286  		}
   287  		nv := ""
   288  		if len(args) == arrow+2 {
   289  			if !IsDirectoryPath(ns) {
   290  				fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)\n", f.Syntax.Name, line.Start.Line)
   291  				return
   292  			}
   293  			if filepath.Separator == '/' && strings.Contains(ns, `\`) {
   294  				fmt.Fprintf(errs, "%s:%d: replacement directory appears to be Windows path (on a non-windows system)\n", f.Syntax.Name, line.Start.Line)
   295  				return
   296  			}
   297  		}
   298  		if len(args) == arrow+3 {
   299  			old := args[arrow+1]
   300  			nv, err = parseVersion(ns, &args[arrow+2], fix)
   301  			if err != nil {
   302  				fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err)
   303  				return
   304  			}
   305  			if IsDirectoryPath(ns) {
   306  				fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version\n", f.Syntax.Name, line.Start.Line, ns)
   307  				return
   308  			}
   309  		}
   310  		f.Replace = append(f.Replace, &Replace{
   311  			Old:    module.Version{Path: s, Version: v},
   312  			New:    module.Version{Path: ns, Version: nv},
   313  			Syntax: line,
   314  		})
   315  	}
   316  }
   317  
   318  // isIndirect reports whether line has a "// indirect" comment,
   319  // meaning it is in go.mod only for its effect on indirect dependencies,
   320  // so that it can be dropped entirely once the effective version of the
   321  // indirect dependency reaches the given minimum version.
   322  func isIndirect(line *Line) bool {
   323  	if len(line.Suffix) == 0 {
   324  		return false
   325  	}
   326  	f := strings.Fields(line.Suffix[0].Token)
   327  	return (len(f) == 2 && f[1] == "indirect" || len(f) > 2 && f[1] == "indirect;") && f[0] == "//"
   328  }
   329  
   330  // setIndirect sets line to have (or not have) a "// indirect" comment.
   331  func setIndirect(line *Line, indirect bool) {
   332  	if isIndirect(line) == indirect {
   333  		return
   334  	}
   335  	if indirect {
   336  		// Adding comment.
   337  		if len(line.Suffix) == 0 {
   338  			// New comment.
   339  			line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
   340  			return
   341  		}
   342  		// Insert at beginning of existing comment.
   343  		com := &line.Suffix[0]
   344  		space := " "
   345  		if len(com.Token) > 2 && com.Token[2] == ' ' || com.Token[2] == '\t' {
   346  			space = ""
   347  		}
   348  		com.Token = "// indirect;" + space + com.Token[2:]
   349  		return
   350  	}
   351  
   352  	// Removing comment.
   353  	f := strings.Fields(line.Suffix[0].Token)
   354  	if len(f) == 2 {
   355  		// Remove whole comment.
   356  		line.Suffix = nil
   357  		return
   358  	}
   359  
   360  	// Remove comment prefix.
   361  	com := &line.Suffix[0]
   362  	i := strings.Index(com.Token, "indirect;")
   363  	com.Token = "//" + com.Token[i+len("indirect;"):]
   364  }
   365  
   366  // IsDirectoryPath reports whether the given path should be interpreted
   367  // as a directory path. Just like on the go command line, relative paths
   368  // and rooted paths are directory paths; the rest are module paths.
   369  func IsDirectoryPath(ns string) bool {
   370  	// Because go.mod files can move from one system to another,
   371  	// we check all known path syntaxes, both Unix and Windows.
   372  	return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
   373  		strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) ||
   374  		len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
   375  }
   376  
   377  // MustQuote reports whether s must be quoted in order to appear as
   378  // a single token in a go.mod line.
   379  func MustQuote(s string) bool {
   380  	for _, r := range s {
   381  		if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' {
   382  			return true
   383  		}
   384  	}
   385  	return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
   386  }
   387  
   388  // AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
   389  // the quotation of s.
   390  func AutoQuote(s string) string {
   391  	if MustQuote(s) {
   392  		return strconv.Quote(s)
   393  	}
   394  	return s
   395  }
   396  
   397  func parseString(s *string) (string, error) {
   398  	t := *s
   399  	if strings.HasPrefix(t, `"`) {
   400  		var err error
   401  		if t, err = strconv.Unquote(t); err != nil {
   402  			return "", err
   403  		}
   404  	} else if strings.ContainsAny(t, "\"'`") {
   405  		// Other quotes are reserved both for possible future expansion
   406  		// and to avoid confusion. For example if someone types 'x'
   407  		// we want that to be a syntax error and not a literal x in literal quotation marks.
   408  		return "", fmt.Errorf("unquoted string cannot contain quote")
   409  	}
   410  	*s = AutoQuote(t)
   411  	return t, nil
   412  }
   413  
   414  func parseVersion(path string, s *string, fix VersionFixer) (string, error) {
   415  	t, err := parseString(s)
   416  	if err != nil {
   417  		return "", err
   418  	}
   419  	if fix != nil {
   420  		var err error
   421  		t, err = fix(path, t)
   422  		if err != nil {
   423  			return "", err
   424  		}
   425  	}
   426  	if v := module.CanonicalVersion(t); v != "" {
   427  		*s = v
   428  		return *s, nil
   429  	}
   430  	return "", fmt.Errorf("version must be of the form v1.2.3")
   431  }
   432  
   433  func modulePathMajor(path string) (string, error) {
   434  	_, major, ok := module.SplitPathVersion(path)
   435  	if !ok {
   436  		return "", fmt.Errorf("invalid module path")
   437  	}
   438  	return major, nil
   439  }
   440  
   441  func (f *File) Format() ([]byte, error) {
   442  	return Format(f.Syntax), nil
   443  }
   444  
   445  // Cleanup cleans up the file f after any edit operations.
   446  // To avoid quadratic behavior, modifications like DropRequire
   447  // clear the entry but do not remove it from the slice.
   448  // Cleanup cleans out all the cleared entries.
   449  func (f *File) Cleanup() {
   450  	w := 0
   451  	for _, r := range f.Require {
   452  		if r.Mod.Path != "" {
   453  			f.Require[w] = r
   454  			w++
   455  		}
   456  	}
   457  	f.Require = f.Require[:w]
   458  
   459  	w = 0
   460  	for _, x := range f.Exclude {
   461  		if x.Mod.Path != "" {
   462  			f.Exclude[w] = x
   463  			w++
   464  		}
   465  	}
   466  	f.Exclude = f.Exclude[:w]
   467  
   468  	w = 0
   469  	for _, r := range f.Replace {
   470  		if r.Old.Path != "" {
   471  			f.Replace[w] = r
   472  			w++
   473  		}
   474  	}
   475  	f.Replace = f.Replace[:w]
   476  
   477  	f.Syntax.Cleanup()
   478  }
   479  
   480  func (f *File) AddGoStmt(version string) error {
   481  	if !GoVersionRE.MatchString(version) {
   482  		return fmt.Errorf("invalid language version string %q", version)
   483  	}
   484  	if f.Go == nil {
   485  		f.Go = &Go{
   486  			Version: version,
   487  			Syntax:  f.Syntax.addLine(nil, "go", version),
   488  		}
   489  	} else {
   490  		f.Go.Version = version
   491  		f.Syntax.updateLine(f.Go.Syntax, "go", version)
   492  	}
   493  	return nil
   494  }
   495  
   496  func (f *File) AddRequire(path, vers string) error {
   497  	need := true
   498  	for _, r := range f.Require {
   499  		if r.Mod.Path == path {
   500  			if need {
   501  				r.Mod.Version = vers
   502  				f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
   503  				need = false
   504  			} else {
   505  				f.Syntax.removeLine(r.Syntax)
   506  				*r = Require{}
   507  			}
   508  		}
   509  	}
   510  
   511  	if need {
   512  		f.AddNewRequire(path, vers, false)
   513  	}
   514  	return nil
   515  }
   516  
   517  func (f *File) AddNewRequire(path, vers string, indirect bool) {
   518  	line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
   519  	setIndirect(line, indirect)
   520  	f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line})
   521  }
   522  
   523  func (f *File) SetRequire(req []*Require) {
   524  	need := make(map[string]string)
   525  	indirect := make(map[string]bool)
   526  	for _, r := range req {
   527  		need[r.Mod.Path] = r.Mod.Version
   528  		indirect[r.Mod.Path] = r.Indirect
   529  	}
   530  
   531  	for _, r := range f.Require {
   532  		if v, ok := need[r.Mod.Path]; ok {
   533  			r.Mod.Version = v
   534  			r.Indirect = indirect[r.Mod.Path]
   535  		}
   536  	}
   537  
   538  	var newStmts []Expr
   539  	for _, stmt := range f.Syntax.Stmt {
   540  		switch stmt := stmt.(type) {
   541  		case *LineBlock:
   542  			if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
   543  				var newLines []*Line
   544  				for _, line := range stmt.Line {
   545  					if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" {
   546  						line.Token[1] = need[p]
   547  						delete(need, p)
   548  						setIndirect(line, indirect[p])
   549  						newLines = append(newLines, line)
   550  					}
   551  				}
   552  				if len(newLines) == 0 {
   553  					continue // drop stmt
   554  				}
   555  				stmt.Line = newLines
   556  			}
   557  
   558  		case *Line:
   559  			if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
   560  				if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" {
   561  					stmt.Token[2] = need[p]
   562  					delete(need, p)
   563  					setIndirect(stmt, indirect[p])
   564  				} else {
   565  					continue // drop stmt
   566  				}
   567  			}
   568  		}
   569  		newStmts = append(newStmts, stmt)
   570  	}
   571  	f.Syntax.Stmt = newStmts
   572  
   573  	for path, vers := range need {
   574  		f.AddNewRequire(path, vers, indirect[path])
   575  	}
   576  	f.SortBlocks()
   577  }
   578  
   579  func (f *File) DropRequire(path string) error {
   580  	for _, r := range f.Require {
   581  		if r.Mod.Path == path {
   582  			f.Syntax.removeLine(r.Syntax)
   583  			*r = Require{}
   584  		}
   585  	}
   586  	return nil
   587  }
   588  
   589  func (f *File) AddExclude(path, vers string) error {
   590  	var hint *Line
   591  	for _, x := range f.Exclude {
   592  		if x.Mod.Path == path && x.Mod.Version == vers {
   593  			return nil
   594  		}
   595  		if x.Mod.Path == path {
   596  			hint = x.Syntax
   597  		}
   598  	}
   599  
   600  	f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
   601  	return nil
   602  }
   603  
   604  func (f *File) DropExclude(path, vers string) error {
   605  	for _, x := range f.Exclude {
   606  		if x.Mod.Path == path && x.Mod.Version == vers {
   607  			f.Syntax.removeLine(x.Syntax)
   608  			*x = Exclude{}
   609  		}
   610  	}
   611  	return nil
   612  }
   613  
   614  func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
   615  	need := true
   616  	old := module.Version{Path: oldPath, Version: oldVers}
   617  	new := module.Version{Path: newPath, Version: newVers}
   618  	tokens := []string{"replace", AutoQuote(oldPath)}
   619  	if oldVers != "" {
   620  		tokens = append(tokens, oldVers)
   621  	}
   622  	tokens = append(tokens, "=>", AutoQuote(newPath))
   623  	if newVers != "" {
   624  		tokens = append(tokens, newVers)
   625  	}
   626  
   627  	var hint *Line
   628  	for _, r := range f.Replace {
   629  		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
   630  			if need {
   631  				// Found replacement for old; update to use new.
   632  				r.New = new
   633  				f.Syntax.updateLine(r.Syntax, tokens...)
   634  				need = false
   635  				continue
   636  			}
   637  			// Already added; delete other replacements for same.
   638  			f.Syntax.removeLine(r.Syntax)
   639  			*r = Replace{}
   640  		}
   641  		if r.Old.Path == oldPath {
   642  			hint = r.Syntax
   643  		}
   644  	}
   645  	if need {
   646  		f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
   647  	}
   648  	return nil
   649  }
   650  
   651  func (f *File) DropReplace(oldPath, oldVers string) error {
   652  	for _, r := range f.Replace {
   653  		if r.Old.Path == oldPath && r.Old.Version == oldVers {
   654  			f.Syntax.removeLine(r.Syntax)
   655  			*r = Replace{}
   656  		}
   657  	}
   658  	return nil
   659  }
   660  
   661  func (f *File) SortBlocks() {
   662  	f.removeDups() // otherwise sorting is unsafe
   663  
   664  	for _, stmt := range f.Syntax.Stmt {
   665  		block, ok := stmt.(*LineBlock)
   666  		if !ok {
   667  			continue
   668  		}
   669  		sort.Slice(block.Line, func(i, j int) bool {
   670  			li := block.Line[i]
   671  			lj := block.Line[j]
   672  			for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
   673  				if li.Token[k] != lj.Token[k] {
   674  					return li.Token[k] < lj.Token[k]
   675  				}
   676  			}
   677  			return len(li.Token) < len(lj.Token)
   678  		})
   679  	}
   680  }
   681  
   682  func (f *File) removeDups() {
   683  	have := make(map[module.Version]bool)
   684  	kill := make(map[*Line]bool)
   685  	for _, x := range f.Exclude {
   686  		if have[x.Mod] {
   687  			kill[x.Syntax] = true
   688  			continue
   689  		}
   690  		have[x.Mod] = true
   691  	}
   692  	var excl []*Exclude
   693  	for _, x := range f.Exclude {
   694  		if !kill[x.Syntax] {
   695  			excl = append(excl, x)
   696  		}
   697  	}
   698  	f.Exclude = excl
   699  
   700  	have = make(map[module.Version]bool)
   701  	// Later replacements take priority over earlier ones.
   702  	for i := len(f.Replace) - 1; i >= 0; i-- {
   703  		x := f.Replace[i]
   704  		if have[x.Old] {
   705  			kill[x.Syntax] = true
   706  			continue
   707  		}
   708  		have[x.Old] = true
   709  	}
   710  	var repl []*Replace
   711  	for _, x := range f.Replace {
   712  		if !kill[x.Syntax] {
   713  			repl = append(repl, x)
   714  		}
   715  	}
   716  	f.Replace = repl
   717  
   718  	var stmts []Expr
   719  	for _, stmt := range f.Syntax.Stmt {
   720  		switch stmt := stmt.(type) {
   721  		case *Line:
   722  			if kill[stmt] {
   723  				continue
   724  			}
   725  		case *LineBlock:
   726  			var lines []*Line
   727  			for _, line := range stmt.Line {
   728  				if !kill[line] {
   729  					lines = append(lines, line)
   730  				}
   731  			}
   732  			stmt.Line = lines
   733  			if len(lines) == 0 {
   734  				continue
   735  			}
   736  		}
   737  		stmts = append(stmts, stmt)
   738  	}
   739  	f.Syntax.Stmt = stmts
   740  }