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