.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / mod@v0.4.1 / modfile / rule.go
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         Syntax *Line
52 }
53
54 // A Go is the go statement.
55 type Go struct {
56         Version string // "1.23"
57         Syntax  *Line
58 }
59
60 // A Require is a single require statement.
61 type Require struct {
62         Mod      module.Version
63         Indirect bool // has "// indirect" comment
64         Syntax   *Line
65 }
66
67 // An Exclude is a single exclude statement.
68 type Exclude struct {
69         Mod    module.Version
70         Syntax *Line
71 }
72
73 // A Replace is a single replace statement.
74 type Replace struct {
75         Old    module.Version
76         New    module.Version
77         Syntax *Line
78 }
79
80 // A Retract is a single retract statement.
81 type Retract struct {
82         VersionInterval
83         Rationale string
84         Syntax    *Line
85 }
86
87 // A VersionInterval represents a range of versions with upper and lower bounds.
88 // Intervals are closed: both bounds are included. When Low is equal to High,
89 // the interval may refer to a single version ('v1.2.3') or an interval
90 // ('[v1.2.3, v1.2.3]'); both have the same representation.
91 type VersionInterval struct {
92         Low, High string
93 }
94
95 func (f *File) AddModuleStmt(path string) error {
96         if f.Syntax == nil {
97                 f.Syntax = new(FileSyntax)
98         }
99         if f.Module == nil {
100                 f.Module = &Module{
101                         Mod:    module.Version{Path: path},
102                         Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
103                 }
104         } else {
105                 f.Module.Mod.Path = path
106                 f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
107         }
108         return nil
109 }
110
111 func (f *File) AddComment(text string) {
112         if f.Syntax == nil {
113                 f.Syntax = new(FileSyntax)
114         }
115         f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
116                 Comments: Comments{
117                         Before: []Comment{
118                                 {
119                                         Token: text,
120                                 },
121                         },
122                 },
123         })
124 }
125
126 type VersionFixer func(path, version string) (string, error)
127
128 // Parse parses the data, reported in errors as being from file,
129 // into a File struct. It applies fix, if non-nil, to canonicalize all module versions found.
130 func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
131         return parseToFile(file, data, fix, true)
132 }
133
134 // ParseLax is like Parse but ignores unknown statements.
135 // It is used when parsing go.mod files other than the main module,
136 // under the theory that most statement types we add in the future will
137 // only apply in the main module, like exclude and replace,
138 // and so we get better gradual deployments if old go commands
139 // simply ignore those statements when found in go.mod files
140 // in dependencies.
141 func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
142         return parseToFile(file, data, fix, false)
143 }
144
145 func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) {
146         fs, err := parse(file, data)
147         if err != nil {
148                 return nil, err
149         }
150         f := &File{
151                 Syntax: fs,
152         }
153
154         var errs ErrorList
155         for _, x := range fs.Stmt {
156                 switch x := x.(type) {
157                 case *Line:
158                         f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
159
160                 case *LineBlock:
161                         if len(x.Token) > 1 {
162                                 if strict {
163                                         errs = append(errs, Error{
164                                                 Filename: file,
165                                                 Pos:      x.Start,
166                                                 Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
167                                         })
168                                 }
169                                 continue
170                         }
171                         switch x.Token[0] {
172                         default:
173                                 if strict {
174                                         errs = append(errs, Error{
175                                                 Filename: file,
176                                                 Pos:      x.Start,
177                                                 Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
178                                         })
179                                 }
180                                 continue
181                         case "module", "require", "exclude", "replace", "retract":
182                                 for _, l := range x.Line {
183                                         f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
184                                 }
185                         }
186                 }
187         }
188
189         if len(errs) > 0 {
190                 return nil, errs
191         }
192         return f, nil
193 }
194
195 var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
196
197 func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
198         // If strict is false, this module is a dependency.
199         // We ignore all unknown directives as well as main-module-only
200         // directives like replace and exclude. It will work better for
201         // forward compatibility if we can depend on modules that have unknown
202         // statements (presumed relevant only when acting as the main module)
203         // and simply ignore those statements.
204         if !strict {
205                 switch verb {
206                 case "go", "module", "retract", "require":
207                         // want these even for dependency go.mods
208                 default:
209                         return
210                 }
211         }
212
213         wrapModPathError := func(modPath string, err error) {
214                 *errs = append(*errs, Error{
215                         Filename: f.Syntax.Name,
216                         Pos:      line.Start,
217                         ModPath:  modPath,
218                         Verb:     verb,
219                         Err:      err,
220                 })
221         }
222         wrapError := func(err error) {
223                 *errs = append(*errs, Error{
224                         Filename: f.Syntax.Name,
225                         Pos:      line.Start,
226                         Err:      err,
227                 })
228         }
229         errorf := func(format string, args ...interface{}) {
230                 wrapError(fmt.Errorf(format, args...))
231         }
232
233         switch verb {
234         default:
235                 errorf("unknown directive: %s", verb)
236
237         case "go":
238                 if f.Go != nil {
239                         errorf("repeated go statement")
240                         return
241                 }
242                 if len(args) != 1 {
243                         errorf("go directive expects exactly one argument")
244                         return
245                 } else if !GoVersionRE.MatchString(args[0]) {
246                         errorf("invalid go version '%s': must match format 1.23", args[0])
247                         return
248                 }
249
250                 f.Go = &Go{Syntax: line}
251                 f.Go.Version = args[0]
252
253         case "module":
254                 if f.Module != nil {
255                         errorf("repeated module statement")
256                         return
257                 }
258                 f.Module = &Module{Syntax: line}
259                 if len(args) != 1 {
260                         errorf("usage: module module/path")
261                         return
262                 }
263                 s, err := parseString(&args[0])
264                 if err != nil {
265                         errorf("invalid quoted string: %v", err)
266                         return
267                 }
268                 f.Module.Mod = module.Version{Path: s}
269
270         case "require", "exclude":
271                 if len(args) != 2 {
272                         errorf("usage: %s module/path v1.2.3", verb)
273                         return
274                 }
275                 s, err := parseString(&args[0])
276                 if err != nil {
277                         errorf("invalid quoted string: %v", err)
278                         return
279                 }
280                 v, err := parseVersion(verb, s, &args[1], fix)
281                 if err != nil {
282                         wrapError(err)
283                         return
284                 }
285                 pathMajor, err := modulePathMajor(s)
286                 if err != nil {
287                         wrapError(err)
288                         return
289                 }
290                 if err := module.CheckPathMajor(v, pathMajor); err != nil {
291                         wrapModPathError(s, err)
292                         return
293                 }
294                 if verb == "require" {
295                         f.Require = append(f.Require, &Require{
296                                 Mod:      module.Version{Path: s, Version: v},
297                                 Syntax:   line,
298                                 Indirect: isIndirect(line),
299                         })
300                 } else {
301                         f.Exclude = append(f.Exclude, &Exclude{
302                                 Mod:    module.Version{Path: s, Version: v},
303                                 Syntax: line,
304                         })
305                 }
306
307         case "replace":
308                 arrow := 2
309                 if len(args) >= 2 && args[1] == "=>" {
310                         arrow = 1
311                 }
312                 if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
313                         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)
314                         return
315                 }
316                 s, err := parseString(&args[0])
317                 if err != nil {
318                         errorf("invalid quoted string: %v", err)
319                         return
320                 }
321                 pathMajor, err := modulePathMajor(s)
322                 if err != nil {
323                         wrapModPathError(s, err)
324                         return
325                 }
326                 var v string
327                 if arrow == 2 {
328                         v, err = parseVersion(verb, s, &args[1], fix)
329                         if err != nil {
330                                 wrapError(err)
331                                 return
332                         }
333                         if err := module.CheckPathMajor(v, pathMajor); err != nil {
334                                 wrapModPathError(s, err)
335                                 return
336                         }
337                 }
338                 ns, err := parseString(&args[arrow+1])
339                 if err != nil {
340                         errorf("invalid quoted string: %v", err)
341                         return
342                 }
343                 nv := ""
344                 if len(args) == arrow+2 {
345                         if !IsDirectoryPath(ns) {
346                                 errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
347                                 return
348                         }
349                         if filepath.Separator == '/' && strings.Contains(ns, `\`) {
350                                 errorf("replacement directory appears to be Windows path (on a non-windows system)")
351                                 return
352                         }
353                 }
354                 if len(args) == arrow+3 {
355                         nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
356                         if err != nil {
357                                 wrapError(err)
358                                 return
359                         }
360                         if IsDirectoryPath(ns) {
361                                 errorf("replacement module directory path %q cannot have version", ns)
362                                 return
363                         }
364                 }
365                 f.Replace = append(f.Replace, &Replace{
366                         Old:    module.Version{Path: s, Version: v},
367                         New:    module.Version{Path: ns, Version: nv},
368                         Syntax: line,
369                 })
370
371         case "retract":
372                 rationale := parseRetractRationale(block, line)
373                 vi, err := parseVersionInterval(verb, &args, fix)
374                 if err != nil {
375                         if strict {
376                                 wrapError(err)
377                                 return
378                         } else {
379                                 // Only report errors parsing intervals in the main module. We may
380                                 // support additional syntax in the future, such as open and half-open
381                                 // intervals. Those can't be supported now, because they break the
382                                 // go.mod parser, even in lax mode.
383                                 return
384                         }
385                 }
386                 if len(args) > 0 && strict {
387                         // In the future, there may be additional information after the version.
388                         errorf("unexpected token after version: %q", args[0])
389                         return
390                 }
391                 retract := &Retract{
392                         VersionInterval: vi,
393                         Rationale:       rationale,
394                         Syntax:          line,
395                 }
396                 f.Retract = append(f.Retract, retract)
397         }
398 }
399
400 // isIndirect reports whether line has a "// indirect" comment,
401 // meaning it is in go.mod only for its effect on indirect dependencies,
402 // so that it can be dropped entirely once the effective version of the
403 // indirect dependency reaches the given minimum version.
404 func isIndirect(line *Line) bool {
405         if len(line.Suffix) == 0 {
406                 return false
407         }
408         f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
409         return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;")
410 }
411
412 // setIndirect sets line to have (or not have) a "// indirect" comment.
413 func setIndirect(line *Line, indirect bool) {
414         if isIndirect(line) == indirect {
415                 return
416         }
417         if indirect {
418                 // Adding comment.
419                 if len(line.Suffix) == 0 {
420                         // New comment.
421                         line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
422                         return
423                 }
424
425                 com := &line.Suffix[0]
426                 text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash)))
427                 if text == "" {
428                         // Empty comment.
429                         com.Token = "// indirect"
430                         return
431                 }
432
433                 // Insert at beginning of existing comment.
434                 com.Token = "// indirect; " + text
435                 return
436         }
437
438         // Removing comment.
439         f := strings.Fields(line.Suffix[0].Token)
440         if len(f) == 2 {
441                 // Remove whole comment.
442                 line.Suffix = nil
443                 return
444         }
445
446         // Remove comment prefix.
447         com := &line.Suffix[0]
448         i := strings.Index(com.Token, "indirect;")
449         com.Token = "//" + com.Token[i+len("indirect;"):]
450 }
451
452 // IsDirectoryPath reports whether the given path should be interpreted
453 // as a directory path. Just like on the go command line, relative paths
454 // and rooted paths are directory paths; the rest are module paths.
455 func IsDirectoryPath(ns string) bool {
456         // Because go.mod files can move from one system to another,
457         // we check all known path syntaxes, both Unix and Windows.
458         return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
459                 strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) ||
460                 len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
461 }
462
463 // MustQuote reports whether s must be quoted in order to appear as
464 // a single token in a go.mod line.
465 func MustQuote(s string) bool {
466         for _, r := range s {
467                 switch r {
468                 case ' ', '"', '\'', '`':
469                         return true
470
471                 case '(', ')', '[', ']', '{', '}', ',':
472                         if len(s) > 1 {
473                                 return true
474                         }
475
476                 default:
477                         if !unicode.IsPrint(r) {
478                                 return true
479                         }
480                 }
481         }
482         return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
483 }
484
485 // AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
486 // the quotation of s.
487 func AutoQuote(s string) string {
488         if MustQuote(s) {
489                 return strconv.Quote(s)
490         }
491         return s
492 }
493
494 func parseVersionInterval(verb string, args *[]string, fix VersionFixer) (VersionInterval, error) {
495         toks := *args
496         if len(toks) == 0 || toks[0] == "(" {
497                 return VersionInterval{}, fmt.Errorf("expected '[' or version")
498         }
499         if toks[0] != "[" {
500                 v, err := parseVersion(verb, "", &toks[0], fix)
501                 if err != nil {
502                         return VersionInterval{}, err
503                 }
504                 *args = toks[1:]
505                 return VersionInterval{Low: v, High: v}, nil
506         }
507         toks = toks[1:]
508
509         if len(toks) == 0 {
510                 return VersionInterval{}, fmt.Errorf("expected version after '['")
511         }
512         low, err := parseVersion(verb, "", &toks[0], fix)
513         if err != nil {
514                 return VersionInterval{}, err
515         }
516         toks = toks[1:]
517
518         if len(toks) == 0 || toks[0] != "," {
519                 return VersionInterval{}, fmt.Errorf("expected ',' after version")
520         }
521         toks = toks[1:]
522
523         if len(toks) == 0 {
524                 return VersionInterval{}, fmt.Errorf("expected version after ','")
525         }
526         high, err := parseVersion(verb, "", &toks[0], fix)
527         if err != nil {
528                 return VersionInterval{}, err
529         }
530         toks = toks[1:]
531
532         if len(toks) == 0 || toks[0] != "]" {
533                 return VersionInterval{}, fmt.Errorf("expected ']' after version")
534         }
535         toks = toks[1:]
536
537         *args = toks
538         return VersionInterval{Low: low, High: high}, nil
539 }
540
541 func parseString(s *string) (string, error) {
542         t := *s
543         if strings.HasPrefix(t, `"`) {
544                 var err error
545                 if t, err = strconv.Unquote(t); err != nil {
546                         return "", err
547                 }
548         } else if strings.ContainsAny(t, "\"'`") {
549                 // Other quotes are reserved both for possible future expansion
550                 // and to avoid confusion. For example if someone types 'x'
551                 // we want that to be a syntax error and not a literal x in literal quotation marks.
552                 return "", fmt.Errorf("unquoted string cannot contain quote")
553         }
554         *s = AutoQuote(t)
555         return t, nil
556 }
557
558 // parseRetractRationale extracts the rationale for a retract directive from the
559 // surrounding comments. If the line does not have comments and is part of a
560 // block that does have comments, the block's comments are used.
561 func parseRetractRationale(block *LineBlock, line *Line) string {
562         comments := line.Comment()
563         if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
564                 comments = block.Comment()
565         }
566         groups := [][]Comment{comments.Before, comments.Suffix}
567         var lines []string
568         for _, g := range groups {
569                 for _, c := range g {
570                         if !strings.HasPrefix(c.Token, "//") {
571                                 continue // blank line
572                         }
573                         lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
574                 }
575         }
576         return strings.Join(lines, "\n")
577 }
578
579 type ErrorList []Error
580
581 func (e ErrorList) Error() string {
582         errStrs := make([]string, len(e))
583         for i, err := range e {
584                 errStrs[i] = err.Error()
585         }
586         return strings.Join(errStrs, "\n")
587 }
588
589 type Error struct {
590         Filename string
591         Pos      Position
592         Verb     string
593         ModPath  string
594         Err      error
595 }
596
597 func (e *Error) Error() string {
598         var pos string
599         if e.Pos.LineRune > 1 {
600                 // Don't print LineRune if it's 1 (beginning of line).
601                 // It's always 1 except in scanner errors, which are rare.
602                 pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune)
603         } else if e.Pos.Line > 0 {
604                 pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line)
605         } else if e.Filename != "" {
606                 pos = fmt.Sprintf("%s: ", e.Filename)
607         }
608
609         var directive string
610         if e.ModPath != "" {
611                 directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
612         } else if e.Verb != "" {
613                 directive = fmt.Sprintf("%s: ", e.Verb)
614         }
615
616         return pos + directive + e.Err.Error()
617 }
618
619 func (e *Error) Unwrap() error { return e.Err }
620
621 func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
622         t, err := parseString(s)
623         if err != nil {
624                 return "", &Error{
625                         Verb:    verb,
626                         ModPath: path,
627                         Err: &module.InvalidVersionError{
628                                 Version: *s,
629                                 Err:     err,
630                         },
631                 }
632         }
633         if fix != nil {
634                 var err error
635                 t, err = fix(path, t)
636                 if err != nil {
637                         if err, ok := err.(*module.ModuleError); ok {
638                                 return "", &Error{
639                                         Verb:    verb,
640                                         ModPath: path,
641                                         Err:     err.Err,
642                                 }
643                         }
644                         return "", err
645                 }
646         }
647         if v := module.CanonicalVersion(t); v != "" {
648                 *s = v
649                 return *s, nil
650         }
651         return "", &Error{
652                 Verb:    verb,
653                 ModPath: path,
654                 Err: &module.InvalidVersionError{
655                         Version: t,
656                         Err:     errors.New("must be of the form v1.2.3"),
657                 },
658         }
659 }
660
661 func modulePathMajor(path string) (string, error) {
662         _, major, ok := module.SplitPathVersion(path)
663         if !ok {
664                 return "", fmt.Errorf("invalid module path")
665         }
666         return major, nil
667 }
668
669 func (f *File) Format() ([]byte, error) {
670         return Format(f.Syntax), nil
671 }
672
673 // Cleanup cleans up the file f after any edit operations.
674 // To avoid quadratic behavior, modifications like DropRequire
675 // clear the entry but do not remove it from the slice.
676 // Cleanup cleans out all the cleared entries.
677 func (f *File) Cleanup() {
678         w := 0
679         for _, r := range f.Require {
680                 if r.Mod.Path != "" {
681                         f.Require[w] = r
682                         w++
683                 }
684         }
685         f.Require = f.Require[:w]
686
687         w = 0
688         for _, x := range f.Exclude {
689                 if x.Mod.Path != "" {
690                         f.Exclude[w] = x
691                         w++
692                 }
693         }
694         f.Exclude = f.Exclude[:w]
695
696         w = 0
697         for _, r := range f.Replace {
698                 if r.Old.Path != "" {
699                         f.Replace[w] = r
700                         w++
701                 }
702         }
703         f.Replace = f.Replace[:w]
704
705         w = 0
706         for _, r := range f.Retract {
707                 if r.Low != "" || r.High != "" {
708                         f.Retract[w] = r
709                         w++
710                 }
711         }
712         f.Retract = f.Retract[:w]
713
714         f.Syntax.Cleanup()
715 }
716
717 func (f *File) AddGoStmt(version string) error {
718         if !GoVersionRE.MatchString(version) {
719                 return fmt.Errorf("invalid language version string %q", version)
720         }
721         if f.Go == nil {
722                 var hint Expr
723                 if f.Module != nil && f.Module.Syntax != nil {
724                         hint = f.Module.Syntax
725                 }
726                 f.Go = &Go{
727                         Version: version,
728                         Syntax:  f.Syntax.addLine(hint, "go", version),
729                 }
730         } else {
731                 f.Go.Version = version
732                 f.Syntax.updateLine(f.Go.Syntax, "go", version)
733         }
734         return nil
735 }
736
737 func (f *File) AddRequire(path, vers string) error {
738         need := true
739         for _, r := range f.Require {
740                 if r.Mod.Path == path {
741                         if need {
742                                 r.Mod.Version = vers
743                                 f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
744                                 need = false
745                         } else {
746                                 f.Syntax.removeLine(r.Syntax)
747                                 *r = Require{}
748                         }
749                 }
750         }
751
752         if need {
753                 f.AddNewRequire(path, vers, false)
754         }
755         return nil
756 }
757
758 func (f *File) AddNewRequire(path, vers string, indirect bool) {
759         line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
760         setIndirect(line, indirect)
761         f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line})
762 }
763
764 func (f *File) SetRequire(req []*Require) {
765         need := make(map[string]string)
766         indirect := make(map[string]bool)
767         for _, r := range req {
768                 need[r.Mod.Path] = r.Mod.Version
769                 indirect[r.Mod.Path] = r.Indirect
770         }
771
772         for _, r := range f.Require {
773                 if v, ok := need[r.Mod.Path]; ok {
774                         r.Mod.Version = v
775                         r.Indirect = indirect[r.Mod.Path]
776                 } else {
777                         *r = Require{}
778                 }
779         }
780
781         var newStmts []Expr
782         for _, stmt := range f.Syntax.Stmt {
783                 switch stmt := stmt.(type) {
784                 case *LineBlock:
785                         if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
786                                 var newLines []*Line
787                                 for _, line := range stmt.Line {
788                                         if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" {
789                                                 if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
790                                                         line.Comments.Before = line.Comments.Before[:0]
791                                                 }
792                                                 line.Token[1] = need[p]
793                                                 delete(need, p)
794                                                 setIndirect(line, indirect[p])
795                                                 newLines = append(newLines, line)
796                                         }
797                                 }
798                                 if len(newLines) == 0 {
799                                         continue // drop stmt
800                                 }
801                                 stmt.Line = newLines
802                         }
803
804                 case *Line:
805                         if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
806                                 if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" {
807                                         stmt.Token[2] = need[p]
808                                         delete(need, p)
809                                         setIndirect(stmt, indirect[p])
810                                 } else {
811                                         continue // drop stmt
812                                 }
813                         }
814                 }
815                 newStmts = append(newStmts, stmt)
816         }
817         f.Syntax.Stmt = newStmts
818
819         for path, vers := range need {
820                 f.AddNewRequire(path, vers, indirect[path])
821         }
822         f.SortBlocks()
823 }
824
825 func (f *File) DropRequire(path string) error {
826         for _, r := range f.Require {
827                 if r.Mod.Path == path {
828                         f.Syntax.removeLine(r.Syntax)
829                         *r = Require{}
830                 }
831         }
832         return nil
833 }
834
835 // AddExclude adds a exclude statement to the mod file. Errors if the provided
836 // version is not a canonical version string
837 func (f *File) AddExclude(path, vers string) error {
838         if !isCanonicalVersion(vers) {
839                 return &module.InvalidVersionError{
840                         Version: vers,
841                         Err:     errors.New("must be of the form v1.2.3"),
842                 }
843         }
844
845         var hint *Line
846         for _, x := range f.Exclude {
847                 if x.Mod.Path == path && x.Mod.Version == vers {
848                         return nil
849                 }
850                 if x.Mod.Path == path {
851                         hint = x.Syntax
852                 }
853         }
854
855         f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
856         return nil
857 }
858
859 func (f *File) DropExclude(path, vers string) error {
860         for _, x := range f.Exclude {
861                 if x.Mod.Path == path && x.Mod.Version == vers {
862                         f.Syntax.removeLine(x.Syntax)
863                         *x = Exclude{}
864                 }
865         }
866         return nil
867 }
868
869 func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
870         need := true
871         old := module.Version{Path: oldPath, Version: oldVers}
872         new := module.Version{Path: newPath, Version: newVers}
873         tokens := []string{"replace", AutoQuote(oldPath)}
874         if oldVers != "" {
875                 tokens = append(tokens, oldVers)
876         }
877         tokens = append(tokens, "=>", AutoQuote(newPath))
878         if newVers != "" {
879                 tokens = append(tokens, newVers)
880         }
881
882         var hint *Line
883         for _, r := range f.Replace {
884                 if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
885                         if need {
886                                 // Found replacement for old; update to use new.
887                                 r.New = new
888                                 f.Syntax.updateLine(r.Syntax, tokens...)
889                                 need = false
890                                 continue
891                         }
892                         // Already added; delete other replacements for same.
893                         f.Syntax.removeLine(r.Syntax)
894                         *r = Replace{}
895                 }
896                 if r.Old.Path == oldPath {
897                         hint = r.Syntax
898                 }
899         }
900         if need {
901                 f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
902         }
903         return nil
904 }
905
906 func (f *File) DropReplace(oldPath, oldVers string) error {
907         for _, r := range f.Replace {
908                 if r.Old.Path == oldPath && r.Old.Version == oldVers {
909                         f.Syntax.removeLine(r.Syntax)
910                         *r = Replace{}
911                 }
912         }
913         return nil
914 }
915
916 // AddRetract adds a retract statement to the mod file. Errors if the provided
917 // version interval does not consist of canonical version strings
918 func (f *File) AddRetract(vi VersionInterval, rationale string) error {
919         if !isCanonicalVersion(vi.High) {
920                 return &module.InvalidVersionError{
921                         Version: vi.High,
922                         Err:     errors.New("must be of the form v1.2.3"),
923                 }
924         }
925         if !isCanonicalVersion(vi.Low) {
926                 return &module.InvalidVersionError{
927                         Version: vi.Low,
928                         Err:     errors.New("must be of the form v1.2.3"),
929                 }
930         }
931
932         r := &Retract{
933                 VersionInterval: vi,
934         }
935         if vi.Low == vi.High {
936                 r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
937         } else {
938                 r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
939         }
940         if rationale != "" {
941                 for _, line := range strings.Split(rationale, "\n") {
942                         com := Comment{Token: "// " + line}
943                         r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
944                 }
945         }
946         return nil
947 }
948
949 func (f *File) DropRetract(vi VersionInterval) error {
950         for _, r := range f.Retract {
951                 if r.VersionInterval == vi {
952                         f.Syntax.removeLine(r.Syntax)
953                         *r = Retract{}
954                 }
955         }
956         return nil
957 }
958
959 func (f *File) SortBlocks() {
960         f.removeDups() // otherwise sorting is unsafe
961
962         for _, stmt := range f.Syntax.Stmt {
963                 block, ok := stmt.(*LineBlock)
964                 if !ok {
965                         continue
966                 }
967                 less := lineLess
968                 if block.Token[0] == "retract" {
969                         less = lineRetractLess
970                 }
971                 sort.SliceStable(block.Line, func(i, j int) bool {
972                         return less(block.Line[i], block.Line[j])
973                 })
974         }
975 }
976
977 // removeDups removes duplicate exclude and replace directives.
978 //
979 // Earlier exclude directives take priority.
980 //
981 // Later replace directives take priority.
982 //
983 // require directives are not de-duplicated. That's left up to higher-level
984 // logic (MVS).
985 //
986 // retract directives are not de-duplicated since comments are
987 // meaningful, and versions may be retracted multiple times.
988 func (f *File) removeDups() {
989         kill := make(map[*Line]bool)
990
991         // Remove duplicate excludes.
992         haveExclude := make(map[module.Version]bool)
993         for _, x := range f.Exclude {
994                 if haveExclude[x.Mod] {
995                         kill[x.Syntax] = true
996                         continue
997                 }
998                 haveExclude[x.Mod] = true
999         }
1000         var excl []*Exclude
1001         for _, x := range f.Exclude {
1002                 if !kill[x.Syntax] {
1003                         excl = append(excl, x)
1004                 }
1005         }
1006         f.Exclude = excl
1007
1008         // Remove duplicate replacements.
1009         // Later replacements take priority over earlier ones.
1010         haveReplace := make(map[module.Version]bool)
1011         for i := len(f.Replace) - 1; i >= 0; i-- {
1012                 x := f.Replace[i]
1013                 if haveReplace[x.Old] {
1014                         kill[x.Syntax] = true
1015                         continue
1016                 }
1017                 haveReplace[x.Old] = true
1018         }
1019         var repl []*Replace
1020         for _, x := range f.Replace {
1021                 if !kill[x.Syntax] {
1022                         repl = append(repl, x)
1023                 }
1024         }
1025         f.Replace = repl
1026
1027         // Duplicate require and retract directives are not removed.
1028
1029         // Drop killed statements from the syntax tree.
1030         var stmts []Expr
1031         for _, stmt := range f.Syntax.Stmt {
1032                 switch stmt := stmt.(type) {
1033                 case *Line:
1034                         if kill[stmt] {
1035                                 continue
1036                         }
1037                 case *LineBlock:
1038                         var lines []*Line
1039                         for _, line := range stmt.Line {
1040                                 if !kill[line] {
1041                                         lines = append(lines, line)
1042                                 }
1043                         }
1044                         stmt.Line = lines
1045                         if len(lines) == 0 {
1046                                 continue
1047                         }
1048                 }
1049                 stmts = append(stmts, stmt)
1050         }
1051         f.Syntax.Stmt = stmts
1052 }
1053
1054 // lineLess returns whether li should be sorted before lj. It sorts
1055 // lexicographically without assigning any special meaning to tokens.
1056 func lineLess(li, lj *Line) bool {
1057         for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
1058                 if li.Token[k] != lj.Token[k] {
1059                         return li.Token[k] < lj.Token[k]
1060                 }
1061         }
1062         return len(li.Token) < len(lj.Token)
1063 }
1064
1065 // lineRetractLess returns whether li should be sorted before lj for lines in
1066 // a "retract" block. It treats each line as a version interval. Single versions
1067 // are compared as if they were intervals with the same low and high version.
1068 // Intervals are sorted in descending order, first by low version, then by
1069 // high version, using semver.Compare.
1070 func lineRetractLess(li, lj *Line) bool {
1071         interval := func(l *Line) VersionInterval {
1072                 if len(l.Token) == 1 {
1073                         return VersionInterval{Low: l.Token[0], High: l.Token[0]}
1074                 } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
1075                         return VersionInterval{Low: l.Token[1], High: l.Token[3]}
1076                 } else {
1077                         // Line in unknown format. Treat as an invalid version.
1078                         return VersionInterval{}
1079                 }
1080         }
1081         vii := interval(li)
1082         vij := interval(lj)
1083         if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
1084                 return cmp > 0
1085         }
1086         return semver.Compare(vii.High, vij.High) > 0
1087 }
1088
1089 // isCanonicalVersion tests if the provided version string represents a valid
1090 // canonical version.
1091 func isCanonicalVersion(vers string) bool {
1092         return vers != "" && semver.Canonical(vers) == vers
1093 }