|  | // Copyright 2018 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package modfile | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "errors" | 
|  | "fmt" | 
|  | "path/filepath" | 
|  | "sort" | 
|  | "strconv" | 
|  | "strings" | 
|  | "unicode" | 
|  |  | 
|  | "golang.org/x/mod/internal/lazyregexp" | 
|  | "golang.org/x/mod/module" | 
|  | ) | 
|  |  | 
|  | // A File is the parsed, interpreted form of a go.mod file. | 
|  | type File struct { | 
|  | Module  *Module | 
|  | Go      *Go | 
|  | Require []*Require | 
|  | Exclude []*Exclude | 
|  | Replace []*Replace | 
|  |  | 
|  | Syntax *FileSyntax | 
|  | } | 
|  |  | 
|  | // A Module is the module statement. | 
|  | type Module struct { | 
|  | Mod    module.Version | 
|  | Syntax *Line | 
|  | } | 
|  |  | 
|  | // A Go is the go statement. | 
|  | type Go struct { | 
|  | Version string // "1.23" | 
|  | Syntax  *Line | 
|  | } | 
|  |  | 
|  | // A Require is a single require statement. | 
|  | type Require struct { | 
|  | Mod      module.Version | 
|  | Indirect bool // has "// indirect" comment | 
|  | Syntax   *Line | 
|  | } | 
|  |  | 
|  | // An Exclude is a single exclude statement. | 
|  | type Exclude struct { | 
|  | Mod    module.Version | 
|  | Syntax *Line | 
|  | } | 
|  |  | 
|  | // A Replace is a single replace statement. | 
|  | type Replace struct { | 
|  | Old    module.Version | 
|  | New    module.Version | 
|  | Syntax *Line | 
|  | } | 
|  |  | 
|  | func (f *File) AddModuleStmt(path string) error { | 
|  | if f.Syntax == nil { | 
|  | f.Syntax = new(FileSyntax) | 
|  | } | 
|  | if f.Module == nil { | 
|  | f.Module = &Module{ | 
|  | Mod:    module.Version{Path: path}, | 
|  | Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), | 
|  | } | 
|  | } else { | 
|  | f.Module.Mod.Path = path | 
|  | f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) AddComment(text string) { | 
|  | if f.Syntax == nil { | 
|  | f.Syntax = new(FileSyntax) | 
|  | } | 
|  | f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ | 
|  | Comments: Comments{ | 
|  | Before: []Comment{ | 
|  | { | 
|  | Token: text, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | type VersionFixer func(path, version string) (string, error) | 
|  |  | 
|  | // Parse parses the data, reported in errors as being from file, | 
|  | // into a File struct. It applies fix, if non-nil, to canonicalize all module versions found. | 
|  | func Parse(file string, data []byte, fix VersionFixer) (*File, error) { | 
|  | return parseToFile(file, data, fix, true) | 
|  | } | 
|  |  | 
|  | // ParseLax is like Parse but ignores unknown statements. | 
|  | // It is used when parsing go.mod files other than the main module, | 
|  | // under the theory that most statement types we add in the future will | 
|  | // only apply in the main module, like exclude and replace, | 
|  | // and so we get better gradual deployments if old go commands | 
|  | // simply ignore those statements when found in go.mod files | 
|  | // in dependencies. | 
|  | func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { | 
|  | return parseToFile(file, data, fix, false) | 
|  | } | 
|  |  | 
|  | func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) { | 
|  | fs, err := parse(file, data) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | f := &File{ | 
|  | Syntax: fs, | 
|  | } | 
|  |  | 
|  | var errs bytes.Buffer | 
|  | for _, x := range fs.Stmt { | 
|  | switch x := x.(type) { | 
|  | case *Line: | 
|  | f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict) | 
|  |  | 
|  | case *LineBlock: | 
|  | if len(x.Token) > 1 { | 
|  | if strict { | 
|  | fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) | 
|  | } | 
|  | continue | 
|  | } | 
|  | switch x.Token[0] { | 
|  | default: | 
|  | if strict { | 
|  | fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) | 
|  | } | 
|  | continue | 
|  | case "module", "require", "exclude", "replace": | 
|  | for _, l := range x.Line { | 
|  | f.add(&errs, l, x.Token[0], l.Token, fix, strict) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if errs.Len() > 0 { | 
|  | return nil, errors.New(strings.TrimRight(errs.String(), "\n")) | 
|  | } | 
|  | return f, nil | 
|  | } | 
|  |  | 
|  | var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) | 
|  |  | 
|  | func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) { | 
|  | // If strict is false, this module is a dependency. | 
|  | // We ignore all unknown directives as well as main-module-only | 
|  | // directives like replace and exclude. It will work better for | 
|  | // forward compatibility if we can depend on modules that have unknown | 
|  | // statements (presumed relevant only when acting as the main module) | 
|  | // and simply ignore those statements. | 
|  | if !strict { | 
|  | switch verb { | 
|  | case "module", "require", "go": | 
|  | // want these even for dependency go.mods | 
|  | default: | 
|  | return | 
|  | } | 
|  | } | 
|  |  | 
|  | switch verb { | 
|  | default: | 
|  | fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb) | 
|  |  | 
|  | case "go": | 
|  | if f.Go != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line) | 
|  | return | 
|  | } | 
|  | if len(args) != 1 || !GoVersionRE.MatchString(args[0]) { | 
|  | fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line) | 
|  | return | 
|  | } | 
|  | f.Go = &Go{Syntax: line} | 
|  | f.Go.Version = args[0] | 
|  | case "module": | 
|  | if f.Module != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line) | 
|  | return | 
|  | } | 
|  | f.Module = &Module{Syntax: line} | 
|  | if len(args) != 1 { | 
|  |  | 
|  | fmt.Fprintf(errs, "%s:%d: usage: module module/path\n", f.Syntax.Name, line.Start.Line) | 
|  | return | 
|  | } | 
|  | s, err := parseString(&args[0]) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | f.Module.Mod = module.Version{Path: s} | 
|  | case "require", "exclude": | 
|  | if len(args) != 2 { | 
|  | fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3\n", f.Syntax.Name, line.Start.Line, verb) | 
|  | return | 
|  | } | 
|  | s, err := parseString(&args[0]) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | v, err := parseVersion(verb, s, &args[1], fix) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | pathMajor, err := modulePathMajor(s) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | if err := module.CheckPathMajor(v, pathMajor); err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, &Error{Verb: verb, ModPath: s, Err: err}) | 
|  | return | 
|  | } | 
|  | if verb == "require" { | 
|  | f.Require = append(f.Require, &Require{ | 
|  | Mod:      module.Version{Path: s, Version: v}, | 
|  | Syntax:   line, | 
|  | Indirect: isIndirect(line), | 
|  | }) | 
|  | } else { | 
|  | f.Exclude = append(f.Exclude, &Exclude{ | 
|  | Mod:    module.Version{Path: s, Version: v}, | 
|  | Syntax: line, | 
|  | }) | 
|  | } | 
|  | case "replace": | 
|  | arrow := 2 | 
|  | if len(args) >= 2 && args[1] == "=>" { | 
|  | arrow = 1 | 
|  | } | 
|  | if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { | 
|  | 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) | 
|  | return | 
|  | } | 
|  | s, err := parseString(&args[0]) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | pathMajor, err := modulePathMajor(s) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | var v string | 
|  | if arrow == 2 { | 
|  | v, err = parseVersion(verb, s, &args[1], fix) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | if err := module.CheckPathMajor(v, pathMajor); err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, &Error{Verb: verb, ModPath: s, Err: err}) | 
|  | return | 
|  | } | 
|  | } | 
|  | ns, err := parseString(&args[arrow+1]) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | nv := "" | 
|  | if len(args) == arrow+2 { | 
|  | if !IsDirectoryPath(ns) { | 
|  | 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) | 
|  | return | 
|  | } | 
|  | if filepath.Separator == '/' && strings.Contains(ns, `\`) { | 
|  | fmt.Fprintf(errs, "%s:%d: replacement directory appears to be Windows path (on a non-windows system)\n", f.Syntax.Name, line.Start.Line) | 
|  | return | 
|  | } | 
|  | } | 
|  | if len(args) == arrow+3 { | 
|  | nv, err = parseVersion(verb, ns, &args[arrow+2], fix) | 
|  | if err != nil { | 
|  | fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) | 
|  | return | 
|  | } | 
|  | if IsDirectoryPath(ns) { | 
|  | fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version\n", f.Syntax.Name, line.Start.Line, ns) | 
|  | return | 
|  | } | 
|  | } | 
|  | f.Replace = append(f.Replace, &Replace{ | 
|  | Old:    module.Version{Path: s, Version: v}, | 
|  | New:    module.Version{Path: ns, Version: nv}, | 
|  | Syntax: line, | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | // isIndirect reports whether line has a "// indirect" comment, | 
|  | // meaning it is in go.mod only for its effect on indirect dependencies, | 
|  | // so that it can be dropped entirely once the effective version of the | 
|  | // indirect dependency reaches the given minimum version. | 
|  | func isIndirect(line *Line) bool { | 
|  | if len(line.Suffix) == 0 { | 
|  | return false | 
|  | } | 
|  | f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) | 
|  | return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;") | 
|  | } | 
|  |  | 
|  | // setIndirect sets line to have (or not have) a "// indirect" comment. | 
|  | func setIndirect(line *Line, indirect bool) { | 
|  | if isIndirect(line) == indirect { | 
|  | return | 
|  | } | 
|  | if indirect { | 
|  | // Adding comment. | 
|  | if len(line.Suffix) == 0 { | 
|  | // New comment. | 
|  | line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} | 
|  | return | 
|  | } | 
|  |  | 
|  | com := &line.Suffix[0] | 
|  | text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash))) | 
|  | if text == "" { | 
|  | // Empty comment. | 
|  | com.Token = "// indirect" | 
|  | return | 
|  | } | 
|  |  | 
|  | // Insert at beginning of existing comment. | 
|  | com.Token = "// indirect; " + text | 
|  | return | 
|  | } | 
|  |  | 
|  | // Removing comment. | 
|  | f := strings.Fields(line.Suffix[0].Token) | 
|  | if len(f) == 2 { | 
|  | // Remove whole comment. | 
|  | line.Suffix = nil | 
|  | return | 
|  | } | 
|  |  | 
|  | // Remove comment prefix. | 
|  | com := &line.Suffix[0] | 
|  | i := strings.Index(com.Token, "indirect;") | 
|  | com.Token = "//" + com.Token[i+len("indirect;"):] | 
|  | } | 
|  |  | 
|  | // IsDirectoryPath reports whether the given path should be interpreted | 
|  | // as a directory path. Just like on the go command line, relative paths | 
|  | // and rooted paths are directory paths; the rest are module paths. | 
|  | func IsDirectoryPath(ns string) bool { | 
|  | // Because go.mod files can move from one system to another, | 
|  | // we check all known path syntaxes, both Unix and Windows. | 
|  | return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") || | 
|  | strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) || | 
|  | len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' | 
|  | } | 
|  |  | 
|  | // MustQuote reports whether s must be quoted in order to appear as | 
|  | // a single token in a go.mod line. | 
|  | func MustQuote(s string) bool { | 
|  | for _, r := range s { | 
|  | if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") | 
|  | } | 
|  |  | 
|  | // AutoQuote returns s or, if quoting is required for s to appear in a go.mod, | 
|  | // the quotation of s. | 
|  | func AutoQuote(s string) string { | 
|  | if MustQuote(s) { | 
|  | return strconv.Quote(s) | 
|  | } | 
|  | return s | 
|  | } | 
|  |  | 
|  | func parseString(s *string) (string, error) { | 
|  | t := *s | 
|  | if strings.HasPrefix(t, `"`) { | 
|  | var err error | 
|  | if t, err = strconv.Unquote(t); err != nil { | 
|  | return "", err | 
|  | } | 
|  | } else if strings.ContainsAny(t, "\"'`") { | 
|  | // Other quotes are reserved both for possible future expansion | 
|  | // and to avoid confusion. For example if someone types 'x' | 
|  | // we want that to be a syntax error and not a literal x in literal quotation marks. | 
|  | return "", fmt.Errorf("unquoted string cannot contain quote") | 
|  | } | 
|  | *s = AutoQuote(t) | 
|  | return t, nil | 
|  | } | 
|  |  | 
|  | type Error struct { | 
|  | Verb    string | 
|  | ModPath string | 
|  | Err     error | 
|  | } | 
|  |  | 
|  | func (e *Error) Error() string { | 
|  | return fmt.Sprintf("%s %s: %v", e.Verb, e.ModPath, e.Err) | 
|  | } | 
|  |  | 
|  | func (e *Error) Unwrap() error { return e.Err } | 
|  |  | 
|  | func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) { | 
|  | t, err := parseString(s) | 
|  | if err != nil { | 
|  | return "", &Error{ | 
|  | Verb:    verb, | 
|  | ModPath: path, | 
|  | Err: &module.InvalidVersionError{ | 
|  | Version: *s, | 
|  | Err:     err, | 
|  | }, | 
|  | } | 
|  | } | 
|  | if fix != nil { | 
|  | var err error | 
|  | t, err = fix(path, t) | 
|  | if err != nil { | 
|  | if err, ok := err.(*module.ModuleError); ok { | 
|  | return "", &Error{ | 
|  | Verb:    verb, | 
|  | ModPath: path, | 
|  | Err:     err.Err, | 
|  | } | 
|  | } | 
|  | return "", err | 
|  | } | 
|  | } | 
|  | if v := module.CanonicalVersion(t); v != "" { | 
|  | *s = v | 
|  | return *s, nil | 
|  | } | 
|  | return "", &Error{ | 
|  | Verb:    verb, | 
|  | ModPath: path, | 
|  | Err: &module.InvalidVersionError{ | 
|  | Version: t, | 
|  | Err:     errors.New("must be of the form v1.2.3"), | 
|  | }, | 
|  | } | 
|  | } | 
|  |  | 
|  | func modulePathMajor(path string) (string, error) { | 
|  | _, major, ok := module.SplitPathVersion(path) | 
|  | if !ok { | 
|  | return "", fmt.Errorf("invalid module path") | 
|  | } | 
|  | return major, nil | 
|  | } | 
|  |  | 
|  | func (f *File) Format() ([]byte, error) { | 
|  | return Format(f.Syntax), nil | 
|  | } | 
|  |  | 
|  | // Cleanup cleans up the file f after any edit operations. | 
|  | // To avoid quadratic behavior, modifications like DropRequire | 
|  | // clear the entry but do not remove it from the slice. | 
|  | // Cleanup cleans out all the cleared entries. | 
|  | func (f *File) Cleanup() { | 
|  | w := 0 | 
|  | for _, r := range f.Require { | 
|  | if r.Mod.Path != "" { | 
|  | f.Require[w] = r | 
|  | w++ | 
|  | } | 
|  | } | 
|  | f.Require = f.Require[:w] | 
|  |  | 
|  | w = 0 | 
|  | for _, x := range f.Exclude { | 
|  | if x.Mod.Path != "" { | 
|  | f.Exclude[w] = x | 
|  | w++ | 
|  | } | 
|  | } | 
|  | f.Exclude = f.Exclude[:w] | 
|  |  | 
|  | w = 0 | 
|  | for _, r := range f.Replace { | 
|  | if r.Old.Path != "" { | 
|  | f.Replace[w] = r | 
|  | w++ | 
|  | } | 
|  | } | 
|  | f.Replace = f.Replace[:w] | 
|  |  | 
|  | f.Syntax.Cleanup() | 
|  | } | 
|  |  | 
|  | func (f *File) AddGoStmt(version string) error { | 
|  | if !GoVersionRE.MatchString(version) { | 
|  | return fmt.Errorf("invalid language version string %q", version) | 
|  | } | 
|  | if f.Go == nil { | 
|  | var hint Expr | 
|  | if f.Module != nil && f.Module.Syntax != nil { | 
|  | hint = f.Module.Syntax | 
|  | } | 
|  | f.Go = &Go{ | 
|  | Version: version, | 
|  | Syntax:  f.Syntax.addLine(hint, "go", version), | 
|  | } | 
|  | } else { | 
|  | f.Go.Version = version | 
|  | f.Syntax.updateLine(f.Go.Syntax, "go", version) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) AddRequire(path, vers string) error { | 
|  | need := true | 
|  | for _, r := range f.Require { | 
|  | if r.Mod.Path == path { | 
|  | if need { | 
|  | r.Mod.Version = vers | 
|  | f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) | 
|  | need = false | 
|  | } else { | 
|  | f.Syntax.removeLine(r.Syntax) | 
|  | *r = Require{} | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if need { | 
|  | f.AddNewRequire(path, vers, false) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) AddNewRequire(path, vers string, indirect bool) { | 
|  | line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) | 
|  | setIndirect(line, indirect) | 
|  | f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line}) | 
|  | } | 
|  |  | 
|  | func (f *File) SetRequire(req []*Require) { | 
|  | need := make(map[string]string) | 
|  | indirect := make(map[string]bool) | 
|  | for _, r := range req { | 
|  | need[r.Mod.Path] = r.Mod.Version | 
|  | indirect[r.Mod.Path] = r.Indirect | 
|  | } | 
|  |  | 
|  | for _, r := range f.Require { | 
|  | if v, ok := need[r.Mod.Path]; ok { | 
|  | r.Mod.Version = v | 
|  | r.Indirect = indirect[r.Mod.Path] | 
|  | } else { | 
|  | *r = Require{} | 
|  | } | 
|  | } | 
|  |  | 
|  | var newStmts []Expr | 
|  | for _, stmt := range f.Syntax.Stmt { | 
|  | switch stmt := stmt.(type) { | 
|  | case *LineBlock: | 
|  | if len(stmt.Token) > 0 && stmt.Token[0] == "require" { | 
|  | var newLines []*Line | 
|  | for _, line := range stmt.Line { | 
|  | if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" { | 
|  | if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 { | 
|  | line.Comments.Before = line.Comments.Before[:0] | 
|  | } | 
|  | line.Token[1] = need[p] | 
|  | delete(need, p) | 
|  | setIndirect(line, indirect[p]) | 
|  | newLines = append(newLines, line) | 
|  | } | 
|  | } | 
|  | if len(newLines) == 0 { | 
|  | continue // drop stmt | 
|  | } | 
|  | stmt.Line = newLines | 
|  | } | 
|  |  | 
|  | case *Line: | 
|  | if len(stmt.Token) > 0 && stmt.Token[0] == "require" { | 
|  | if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" { | 
|  | stmt.Token[2] = need[p] | 
|  | delete(need, p) | 
|  | setIndirect(stmt, indirect[p]) | 
|  | } else { | 
|  | continue // drop stmt | 
|  | } | 
|  | } | 
|  | } | 
|  | newStmts = append(newStmts, stmt) | 
|  | } | 
|  | f.Syntax.Stmt = newStmts | 
|  |  | 
|  | for path, vers := range need { | 
|  | f.AddNewRequire(path, vers, indirect[path]) | 
|  | } | 
|  | f.SortBlocks() | 
|  | } | 
|  |  | 
|  | func (f *File) DropRequire(path string) error { | 
|  | for _, r := range f.Require { | 
|  | if r.Mod.Path == path { | 
|  | f.Syntax.removeLine(r.Syntax) | 
|  | *r = Require{} | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) AddExclude(path, vers string) error { | 
|  | var hint *Line | 
|  | for _, x := range f.Exclude { | 
|  | if x.Mod.Path == path && x.Mod.Version == vers { | 
|  | return nil | 
|  | } | 
|  | if x.Mod.Path == path { | 
|  | hint = x.Syntax | 
|  | } | 
|  | } | 
|  |  | 
|  | f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) DropExclude(path, vers string) error { | 
|  | for _, x := range f.Exclude { | 
|  | if x.Mod.Path == path && x.Mod.Version == vers { | 
|  | f.Syntax.removeLine(x.Syntax) | 
|  | *x = Exclude{} | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { | 
|  | need := true | 
|  | old := module.Version{Path: oldPath, Version: oldVers} | 
|  | new := module.Version{Path: newPath, Version: newVers} | 
|  | tokens := []string{"replace", AutoQuote(oldPath)} | 
|  | if oldVers != "" { | 
|  | tokens = append(tokens, oldVers) | 
|  | } | 
|  | tokens = append(tokens, "=>", AutoQuote(newPath)) | 
|  | if newVers != "" { | 
|  | tokens = append(tokens, newVers) | 
|  | } | 
|  |  | 
|  | var hint *Line | 
|  | for _, r := range f.Replace { | 
|  | if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { | 
|  | if need { | 
|  | // Found replacement for old; update to use new. | 
|  | r.New = new | 
|  | f.Syntax.updateLine(r.Syntax, tokens...) | 
|  | need = false | 
|  | continue | 
|  | } | 
|  | // Already added; delete other replacements for same. | 
|  | f.Syntax.removeLine(r.Syntax) | 
|  | *r = Replace{} | 
|  | } | 
|  | if r.Old.Path == oldPath { | 
|  | hint = r.Syntax | 
|  | } | 
|  | } | 
|  | if need { | 
|  | f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)}) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) DropReplace(oldPath, oldVers string) error { | 
|  | for _, r := range f.Replace { | 
|  | if r.Old.Path == oldPath && r.Old.Version == oldVers { | 
|  | f.Syntax.removeLine(r.Syntax) | 
|  | *r = Replace{} | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (f *File) SortBlocks() { | 
|  | f.removeDups() // otherwise sorting is unsafe | 
|  |  | 
|  | for _, stmt := range f.Syntax.Stmt { | 
|  | block, ok := stmt.(*LineBlock) | 
|  | if !ok { | 
|  | continue | 
|  | } | 
|  | sort.Slice(block.Line, func(i, j int) bool { | 
|  | li := block.Line[i] | 
|  | lj := block.Line[j] | 
|  | for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { | 
|  | if li.Token[k] != lj.Token[k] { | 
|  | return li.Token[k] < lj.Token[k] | 
|  | } | 
|  | } | 
|  | return len(li.Token) < len(lj.Token) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func (f *File) removeDups() { | 
|  | have := make(map[module.Version]bool) | 
|  | kill := make(map[*Line]bool) | 
|  | for _, x := range f.Exclude { | 
|  | if have[x.Mod] { | 
|  | kill[x.Syntax] = true | 
|  | continue | 
|  | } | 
|  | have[x.Mod] = true | 
|  | } | 
|  | var excl []*Exclude | 
|  | for _, x := range f.Exclude { | 
|  | if !kill[x.Syntax] { | 
|  | excl = append(excl, x) | 
|  | } | 
|  | } | 
|  | f.Exclude = excl | 
|  |  | 
|  | have = make(map[module.Version]bool) | 
|  | // Later replacements take priority over earlier ones. | 
|  | for i := len(f.Replace) - 1; i >= 0; i-- { | 
|  | x := f.Replace[i] | 
|  | if have[x.Old] { | 
|  | kill[x.Syntax] = true | 
|  | continue | 
|  | } | 
|  | have[x.Old] = true | 
|  | } | 
|  | var repl []*Replace | 
|  | for _, x := range f.Replace { | 
|  | if !kill[x.Syntax] { | 
|  | repl = append(repl, x) | 
|  | } | 
|  | } | 
|  | f.Replace = repl | 
|  |  | 
|  | var stmts []Expr | 
|  | for _, stmt := range f.Syntax.Stmt { | 
|  | switch stmt := stmt.(type) { | 
|  | case *Line: | 
|  | if kill[stmt] { | 
|  | continue | 
|  | } | 
|  | case *LineBlock: | 
|  | var lines []*Line | 
|  | for _, line := range stmt.Line { | 
|  | if !kill[line] { | 
|  | lines = append(lines, line) | 
|  | } | 
|  | } | 
|  | stmt.Line = lines | 
|  | if len(lines) == 0 { | 
|  | continue | 
|  | } | 
|  | } | 
|  | stmts = append(stmts, stmt) | 
|  | } | 
|  | f.Syntax.Stmt = stmts | 
|  | } |