1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package modfile
21
22 import (
23 "cmp"
24 "errors"
25 "fmt"
26 "path/filepath"
27 "slices"
28 "strconv"
29 "strings"
30 "unicode"
31
32 "golang.org/x/mod/internal/lazyregexp"
33 "golang.org/x/mod/module"
34 "golang.org/x/mod/semver"
35 )
36
37
38 type File struct {
39 Module *Module
40 Go *Go
41 Toolchain *Toolchain
42 Godebug []*Godebug
43 Require []*Require
44 Exclude []*Exclude
45 Replace []*Replace
46 Retract []*Retract
47 Tool []*Tool
48 Ignore []*Ignore
49
50 Syntax *FileSyntax
51 }
52
53
54 type Module struct {
55 Mod module.Version
56 Deprecated string
57 Syntax *Line
58 }
59
60
61 type Go struct {
62 Version string
63 Syntax *Line
64 }
65
66
67 type Toolchain struct {
68 Name string
69 Syntax *Line
70 }
71
72
73 type Godebug struct {
74 Key string
75 Value string
76 Syntax *Line
77 }
78
79
80 type Exclude struct {
81 Mod module.Version
82 Syntax *Line
83 }
84
85
86 type Replace struct {
87 Old module.Version
88 New module.Version
89 Syntax *Line
90 }
91
92
93 type Retract struct {
94 VersionInterval
95 Rationale string
96 Syntax *Line
97 }
98
99
100 type Tool struct {
101 Path string
102 Syntax *Line
103 }
104
105
106 type Ignore struct {
107 Path string
108 Syntax *Line
109 }
110
111
112
113
114
115 type VersionInterval struct {
116 Low, High string
117 }
118
119
120 type Require struct {
121 Mod module.Version
122 Indirect bool
123 Syntax *Line
124 }
125
126 func (r *Require) markRemoved() {
127 r.Syntax.markRemoved()
128 *r = Require{}
129 }
130
131 func (r *Require) setVersion(v string) {
132 r.Mod.Version = v
133
134 if line := r.Syntax; len(line.Token) > 0 {
135 if line.InBlock {
136
137
138 if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
139 line.Comments.Before = line.Comments.Before[:0]
140 }
141 if len(line.Token) >= 2 {
142 line.Token[1] = v
143 }
144 } else {
145 if len(line.Token) >= 3 {
146 line.Token[2] = v
147 }
148 }
149 }
150 }
151
152
153 func (r *Require) setIndirect(indirect bool) {
154 r.Indirect = indirect
155 line := r.Syntax
156 if isIndirect(line) == indirect {
157 return
158 }
159 if indirect {
160
161 if len(line.Suffix) == 0 {
162
163 line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
164 return
165 }
166
167 com := &line.Suffix[0]
168 text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash)))
169 if text == "" {
170
171 com.Token = "// indirect"
172 return
173 }
174
175
176 com.Token = "// indirect; " + text
177 return
178 }
179
180
181 f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
182 if f == "indirect" {
183
184 line.Suffix = nil
185 return
186 }
187
188
189 com := &line.Suffix[0]
190 i := strings.Index(com.Token, "indirect;")
191 com.Token = "//" + com.Token[i+len("indirect;"):]
192 }
193
194
195
196
197
198 func isIndirect(line *Line) bool {
199 if len(line.Suffix) == 0 {
200 return false
201 }
202 f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
203 return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;")
204 }
205
206 func (f *File) AddModuleStmt(path string) error {
207 if f.Syntax == nil {
208 f.Syntax = new(FileSyntax)
209 }
210 if f.Module == nil {
211 f.Module = &Module{
212 Mod: module.Version{Path: path},
213 Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
214 }
215 } else {
216 f.Module.Mod.Path = path
217 f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
218 }
219 return nil
220 }
221
222 func (f *File) AddComment(text string) {
223 if f.Syntax == nil {
224 f.Syntax = new(FileSyntax)
225 }
226 f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
227 Comments: Comments{
228 Before: []Comment{
229 {
230 Token: text,
231 },
232 },
233 },
234 })
235 }
236
237 type VersionFixer func(path, version string) (string, error)
238
239
240
241 var dontFixRetract VersionFixer = func(_, vers string) (string, error) {
242 return vers, nil
243 }
244
245
246
247
248
249
250
251
252
253
254 func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
255 return parseToFile(file, data, fix, true)
256 }
257
258
259
260
261
262
263
264
265 func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
266 return parseToFile(file, data, fix, false)
267 }
268
269 func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) {
270 fs, err := parse(file, data)
271 if err != nil {
272 return nil, err
273 }
274 f := &File{
275 Syntax: fs,
276 }
277 var errs ErrorList
278
279
280
281 defer func() {
282 oldLen := len(errs)
283 f.fixRetract(fix, &errs)
284 if len(errs) > oldLen {
285 parsed, err = nil, errs
286 }
287 }()
288
289 for _, x := range fs.Stmt {
290 switch x := x.(type) {
291 case *Line:
292 f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
293
294 case *LineBlock:
295 if len(x.Token) > 1 {
296 if strict {
297 errs = append(errs, Error{
298 Filename: file,
299 Pos: x.Start,
300 Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
301 })
302 }
303 continue
304 }
305 switch x.Token[0] {
306 default:
307 if strict {
308 errs = append(errs, Error{
309 Filename: file,
310 Pos: x.Start,
311 Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
312 })
313 }
314 continue
315 case "module", "godebug", "require", "exclude", "replace", "retract", "tool", "ignore":
316 for _, l := range x.Line {
317 f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
318 }
319 }
320 }
321 }
322
323 if len(errs) > 0 {
324 return nil, errs
325 }
326 return f, nil
327 }
328
329 var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)
330 var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
331
332
333
334
335
336
337 var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`)
338
339 func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
340
341
342
343
344
345
346 if !strict {
347 switch verb {
348 case "go", "module", "retract", "require", "ignore":
349
350 default:
351 return
352 }
353 }
354
355 wrapModPathError := func(modPath string, err error) {
356 *errs = append(*errs, Error{
357 Filename: f.Syntax.Name,
358 Pos: line.Start,
359 ModPath: modPath,
360 Verb: verb,
361 Err: err,
362 })
363 }
364 wrapError := func(err error) {
365 *errs = append(*errs, Error{
366 Filename: f.Syntax.Name,
367 Pos: line.Start,
368 Err: err,
369 })
370 }
371 errorf := func(format string, args ...interface{}) {
372 wrapError(fmt.Errorf(format, args...))
373 }
374
375 switch verb {
376 default:
377 errorf("unknown directive: %s", verb)
378
379 case "go":
380 if f.Go != nil {
381 errorf("repeated go statement")
382 return
383 }
384 if len(args) != 1 {
385 errorf("go directive expects exactly one argument")
386 return
387 } else if !GoVersionRE.MatchString(args[0]) {
388 fixed := false
389 if !strict {
390 if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil {
391 args[0] = m[1]
392 fixed = true
393 }
394 }
395 if !fixed {
396 errorf("invalid go version '%s': must match format 1.23.0", args[0])
397 return
398 }
399 }
400
401 f.Go = &Go{Syntax: line}
402 f.Go.Version = args[0]
403
404 case "toolchain":
405 if f.Toolchain != nil {
406 errorf("repeated toolchain statement")
407 return
408 }
409 if len(args) != 1 {
410 errorf("toolchain directive expects exactly one argument")
411 return
412 } else if !ToolchainRE.MatchString(args[0]) {
413 errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0])
414 return
415 }
416 f.Toolchain = &Toolchain{Syntax: line}
417 f.Toolchain.Name = args[0]
418
419 case "module":
420 if f.Module != nil {
421 errorf("repeated module statement")
422 return
423 }
424 deprecated := parseDeprecation(block, line)
425 f.Module = &Module{
426 Syntax: line,
427 Deprecated: deprecated,
428 }
429 if len(args) != 1 {
430 errorf("usage: module module/path")
431 return
432 }
433 s, err := parseString(&args[0])
434 if err != nil {
435 errorf("invalid quoted string: %v", err)
436 return
437 }
438 f.Module.Mod = module.Version{Path: s}
439
440 case "godebug":
441 if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") {
442 errorf("usage: godebug key=value")
443 return
444 }
445 key, value, ok := strings.Cut(args[0], "=")
446 if !ok {
447 errorf("usage: godebug key=value")
448 return
449 }
450 f.Godebug = append(f.Godebug, &Godebug{
451 Key: key,
452 Value: value,
453 Syntax: line,
454 })
455
456 case "require", "exclude":
457 if len(args) != 2 {
458 errorf("usage: %s module/path v1.2.3", verb)
459 return
460 }
461 s, err := parseString(&args[0])
462 if err != nil {
463 errorf("invalid quoted string: %v", err)
464 return
465 }
466 v, err := parseVersion(verb, s, &args[1], fix)
467 if err != nil {
468 wrapError(err)
469 return
470 }
471 pathMajor, err := modulePathMajor(s)
472 if err != nil {
473 wrapError(err)
474 return
475 }
476 if err := module.CheckPathMajor(v, pathMajor); err != nil {
477 wrapModPathError(s, err)
478 return
479 }
480 if verb == "require" {
481 f.Require = append(f.Require, &Require{
482 Mod: module.Version{Path: s, Version: v},
483 Syntax: line,
484 Indirect: isIndirect(line),
485 })
486 } else {
487 f.Exclude = append(f.Exclude, &Exclude{
488 Mod: module.Version{Path: s, Version: v},
489 Syntax: line,
490 })
491 }
492
493 case "replace":
494 replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
495 if wrappederr != nil {
496 *errs = append(*errs, *wrappederr)
497 return
498 }
499 f.Replace = append(f.Replace, replace)
500
501 case "retract":
502 rationale := parseDirectiveComment(block, line)
503 vi, err := parseVersionInterval(verb, "", &args, dontFixRetract)
504 if err != nil {
505 if strict {
506 wrapError(err)
507 return
508 } else {
509
510
511
512
513 return
514 }
515 }
516 if len(args) > 0 && strict {
517
518 errorf("unexpected token after version: %q", args[0])
519 return
520 }
521 retract := &Retract{
522 VersionInterval: vi,
523 Rationale: rationale,
524 Syntax: line,
525 }
526 f.Retract = append(f.Retract, retract)
527
528 case "tool":
529 if len(args) != 1 {
530 errorf("tool directive expects exactly one argument")
531 return
532 }
533 s, err := parseString(&args[0])
534 if err != nil {
535 errorf("invalid quoted string: %v", err)
536 return
537 }
538 f.Tool = append(f.Tool, &Tool{
539 Path: s,
540 Syntax: line,
541 })
542
543 case "ignore":
544 if len(args) != 1 {
545 errorf("ignore directive expects exactly one argument")
546 return
547 }
548 s, err := parseString(&args[0])
549 if err != nil {
550 errorf("invalid quoted string: %v", err)
551 return
552 }
553 f.Ignore = append(f.Ignore, &Ignore{
554 Path: s,
555 Syntax: line,
556 })
557 }
558 }
559
560 func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) {
561 wrapModPathError := func(modPath string, err error) *Error {
562 return &Error{
563 Filename: filename,
564 Pos: line.Start,
565 ModPath: modPath,
566 Verb: verb,
567 Err: err,
568 }
569 }
570 wrapError := func(err error) *Error {
571 return &Error{
572 Filename: filename,
573 Pos: line.Start,
574 Err: err,
575 }
576 }
577 errorf := func(format string, args ...interface{}) *Error {
578 return wrapError(fmt.Errorf(format, args...))
579 }
580
581 arrow := 2
582 if len(args) >= 2 && args[1] == "=>" {
583 arrow = 1
584 }
585 if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
586 return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
587 }
588 s, err := parseString(&args[0])
589 if err != nil {
590 return nil, errorf("invalid quoted string: %v", err)
591 }
592 pathMajor, err := modulePathMajor(s)
593 if err != nil {
594 return nil, wrapModPathError(s, err)
595
596 }
597 var v string
598 if arrow == 2 {
599 v, err = parseVersion(verb, s, &args[1], fix)
600 if err != nil {
601 return nil, wrapError(err)
602 }
603 if err := module.CheckPathMajor(v, pathMajor); err != nil {
604 return nil, wrapModPathError(s, err)
605 }
606 }
607 ns, err := parseString(&args[arrow+1])
608 if err != nil {
609 return nil, errorf("invalid quoted string: %v", err)
610 }
611 nv := ""
612 if len(args) == arrow+2 {
613 if !IsDirectoryPath(ns) {
614 if strings.Contains(ns, "@") {
615 return nil, errorf("replacement module must match format 'path version', not 'path@version'")
616 }
617 return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)")
618 }
619 if filepath.Separator == '/' && strings.Contains(ns, `\`) {
620 return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
621 }
622 }
623 if len(args) == arrow+3 {
624 nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
625 if err != nil {
626 return nil, wrapError(err)
627 }
628 if IsDirectoryPath(ns) {
629 return nil, errorf("replacement module directory path %q cannot have version", ns)
630 }
631 }
632 return &Replace{
633 Old: module.Version{Path: s, Version: v},
634 New: module.Version{Path: ns, Version: nv},
635 Syntax: line,
636 }, nil
637 }
638
639
640
641
642
643
644
645 func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
646 if fix == nil {
647 return
648 }
649 path := ""
650 if f.Module != nil {
651 path = f.Module.Mod.Path
652 }
653 var r *Retract
654 wrapError := func(err error) {
655 *errs = append(*errs, Error{
656 Filename: f.Syntax.Name,
657 Pos: r.Syntax.Start,
658 Err: err,
659 })
660 }
661
662 for _, r = range f.Retract {
663 if path == "" {
664 wrapError(errors.New("no module directive found, so retract cannot be used"))
665 return
666 }
667
668 args := r.Syntax.Token
669 if args[0] == "retract" {
670 args = args[1:]
671 }
672 vi, err := parseVersionInterval("retract", path, &args, fix)
673 if err != nil {
674 wrapError(err)
675 }
676 r.VersionInterval = vi
677 }
678 }
679
680 func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
681 wrapError := func(err error) {
682 *errs = append(*errs, Error{
683 Filename: f.Syntax.Name,
684 Pos: line.Start,
685 Err: err,
686 })
687 }
688 errorf := func(format string, args ...interface{}) {
689 wrapError(fmt.Errorf(format, args...))
690 }
691
692 switch verb {
693 default:
694 errorf("unknown directive: %s", verb)
695
696 case "go":
697 if f.Go != nil {
698 errorf("repeated go statement")
699 return
700 }
701 if len(args) != 1 {
702 errorf("go directive expects exactly one argument")
703 return
704 } else if !GoVersionRE.MatchString(args[0]) {
705 errorf("invalid go version '%s': must match format 1.23.0", args[0])
706 return
707 }
708
709 f.Go = &Go{Syntax: line}
710 f.Go.Version = args[0]
711
712 case "toolchain":
713 if f.Toolchain != nil {
714 errorf("repeated toolchain statement")
715 return
716 }
717 if len(args) != 1 {
718 errorf("toolchain directive expects exactly one argument")
719 return
720 } else if !ToolchainRE.MatchString(args[0]) {
721 errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0])
722 return
723 }
724
725 f.Toolchain = &Toolchain{Syntax: line}
726 f.Toolchain.Name = args[0]
727
728 case "godebug":
729 if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") {
730 errorf("usage: godebug key=value")
731 return
732 }
733 key, value, ok := strings.Cut(args[0], "=")
734 if !ok {
735 errorf("usage: godebug key=value")
736 return
737 }
738 f.Godebug = append(f.Godebug, &Godebug{
739 Key: key,
740 Value: value,
741 Syntax: line,
742 })
743
744 case "use":
745 if len(args) != 1 {
746 errorf("usage: %s local/dir", verb)
747 return
748 }
749 s, err := parseString(&args[0])
750 if err != nil {
751 errorf("invalid quoted string: %v", err)
752 return
753 }
754 f.Use = append(f.Use, &Use{
755 Path: s,
756 Syntax: line,
757 })
758
759 case "replace":
760 replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
761 if wrappederr != nil {
762 *errs = append(*errs, *wrappederr)
763 return
764 }
765 f.Replace = append(f.Replace, replace)
766 }
767 }
768
769
770
771
772 func IsDirectoryPath(ns string) bool {
773
774
775 return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) ||
776 ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) ||
777 strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) ||
778 len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
779 }
780
781
782
783 func MustQuote(s string) bool {
784 for _, r := range s {
785 switch r {
786 case ' ', '"', '\'', '`':
787 return true
788
789 case '(', ')', '[', ']', '{', '}', ',':
790 if len(s) > 1 {
791 return true
792 }
793
794 default:
795 if !unicode.IsPrint(r) {
796 return true
797 }
798 }
799 }
800 return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
801 }
802
803
804
805 func AutoQuote(s string) string {
806 if MustQuote(s) {
807 return strconv.Quote(s)
808 }
809 return s
810 }
811
812 func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) {
813 toks := *args
814 if len(toks) == 0 || toks[0] == "(" {
815 return VersionInterval{}, fmt.Errorf("expected '[' or version")
816 }
817 if toks[0] != "[" {
818 v, err := parseVersion(verb, path, &toks[0], fix)
819 if err != nil {
820 return VersionInterval{}, err
821 }
822 *args = toks[1:]
823 return VersionInterval{Low: v, High: v}, nil
824 }
825 toks = toks[1:]
826
827 if len(toks) == 0 {
828 return VersionInterval{}, fmt.Errorf("expected version after '['")
829 }
830 low, err := parseVersion(verb, path, &toks[0], fix)
831 if err != nil {
832 return VersionInterval{}, err
833 }
834 toks = toks[1:]
835
836 if len(toks) == 0 || toks[0] != "," {
837 return VersionInterval{}, fmt.Errorf("expected ',' after version")
838 }
839 toks = toks[1:]
840
841 if len(toks) == 0 {
842 return VersionInterval{}, fmt.Errorf("expected version after ','")
843 }
844 high, err := parseVersion(verb, path, &toks[0], fix)
845 if err != nil {
846 return VersionInterval{}, err
847 }
848 toks = toks[1:]
849
850 if len(toks) == 0 || toks[0] != "]" {
851 return VersionInterval{}, fmt.Errorf("expected ']' after version")
852 }
853 toks = toks[1:]
854
855 *args = toks
856 return VersionInterval{Low: low, High: high}, nil
857 }
858
859 func parseString(s *string) (string, error) {
860 t := *s
861 if strings.HasPrefix(t, `"`) {
862 var err error
863 if t, err = strconv.Unquote(t); err != nil {
864 return "", err
865 }
866 } else if strings.ContainsAny(t, "\"'`") {
867
868
869
870 return "", fmt.Errorf("unquoted string cannot contain quote")
871 }
872 *s = AutoQuote(t)
873 return t, nil
874 }
875
876 var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`)
877
878
879
880
881
882
883
884
885
886 func parseDeprecation(block *LineBlock, line *Line) string {
887 text := parseDirectiveComment(block, line)
888 m := deprecatedRE.FindStringSubmatch(text)
889 if m == nil {
890 return ""
891 }
892 return m[1]
893 }
894
895
896
897
898 func parseDirectiveComment(block *LineBlock, line *Line) string {
899 comments := line.Comment()
900 if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
901 comments = block.Comment()
902 }
903 groups := [][]Comment{comments.Before, comments.Suffix}
904 var lines []string
905 for _, g := range groups {
906 for _, c := range g {
907 if !strings.HasPrefix(c.Token, "//") {
908 continue
909 }
910 lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
911 }
912 }
913 return strings.Join(lines, "\n")
914 }
915
916 type ErrorList []Error
917
918 func (e ErrorList) Error() string {
919 errStrs := make([]string, len(e))
920 for i, err := range e {
921 errStrs[i] = err.Error()
922 }
923 return strings.Join(errStrs, "\n")
924 }
925
926 type Error struct {
927 Filename string
928 Pos Position
929 Verb string
930 ModPath string
931 Err error
932 }
933
934 func (e *Error) Error() string {
935 var pos string
936 if e.Pos.LineRune > 1 {
937
938
939 pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune)
940 } else if e.Pos.Line > 0 {
941 pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line)
942 } else if e.Filename != "" {
943 pos = fmt.Sprintf("%s: ", e.Filename)
944 }
945
946 var directive string
947 if e.ModPath != "" {
948 directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
949 } else if e.Verb != "" {
950 directive = fmt.Sprintf("%s: ", e.Verb)
951 }
952
953 return pos + directive + e.Err.Error()
954 }
955
956 func (e *Error) Unwrap() error { return e.Err }
957
958 func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
959 t, err := parseString(s)
960 if err != nil {
961 return "", &Error{
962 Verb: verb,
963 ModPath: path,
964 Err: &module.InvalidVersionError{
965 Version: *s,
966 Err: err,
967 },
968 }
969 }
970 if fix != nil {
971 fixed, err := fix(path, t)
972 if err != nil {
973 if err, ok := err.(*module.ModuleError); ok {
974 return "", &Error{
975 Verb: verb,
976 ModPath: path,
977 Err: err.Err,
978 }
979 }
980 return "", err
981 }
982 t = fixed
983 } else {
984 cv := module.CanonicalVersion(t)
985 if cv == "" {
986 return "", &Error{
987 Verb: verb,
988 ModPath: path,
989 Err: &module.InvalidVersionError{
990 Version: t,
991 Err: errors.New("must be of the form v1.2.3"),
992 },
993 }
994 }
995 t = cv
996 }
997 *s = t
998 return *s, nil
999 }
1000
1001 func modulePathMajor(path string) (string, error) {
1002 _, major, ok := module.SplitPathVersion(path)
1003 if !ok {
1004 return "", fmt.Errorf("invalid module path")
1005 }
1006 return major, nil
1007 }
1008
1009 func (f *File) Format() ([]byte, error) {
1010 return Format(f.Syntax), nil
1011 }
1012
1013
1014
1015
1016
1017 func (f *File) Cleanup() {
1018 w := 0
1019 for _, g := range f.Godebug {
1020 if g.Key != "" {
1021 f.Godebug[w] = g
1022 w++
1023 }
1024 }
1025 f.Godebug = f.Godebug[:w]
1026
1027 w = 0
1028 for _, r := range f.Require {
1029 if r.Mod.Path != "" {
1030 f.Require[w] = r
1031 w++
1032 }
1033 }
1034 f.Require = f.Require[:w]
1035
1036 w = 0
1037 for _, x := range f.Exclude {
1038 if x.Mod.Path != "" {
1039 f.Exclude[w] = x
1040 w++
1041 }
1042 }
1043 f.Exclude = f.Exclude[:w]
1044
1045 w = 0
1046 for _, r := range f.Replace {
1047 if r.Old.Path != "" {
1048 f.Replace[w] = r
1049 w++
1050 }
1051 }
1052 f.Replace = f.Replace[:w]
1053
1054 w = 0
1055 for _, r := range f.Retract {
1056 if r.Low != "" || r.High != "" {
1057 f.Retract[w] = r
1058 w++
1059 }
1060 }
1061 f.Retract = f.Retract[:w]
1062
1063 f.Syntax.Cleanup()
1064 }
1065
1066 func (f *File) AddGoStmt(version string) error {
1067 if !GoVersionRE.MatchString(version) {
1068 return fmt.Errorf("invalid language version %q", version)
1069 }
1070 if f.Go == nil {
1071 var hint Expr
1072 if f.Module != nil && f.Module.Syntax != nil {
1073 hint = f.Module.Syntax
1074 } else if f.Syntax == nil {
1075 f.Syntax = new(FileSyntax)
1076 }
1077 f.Go = &Go{
1078 Version: version,
1079 Syntax: f.Syntax.addLine(hint, "go", version),
1080 }
1081 } else {
1082 f.Go.Version = version
1083 f.Syntax.updateLine(f.Go.Syntax, "go", version)
1084 }
1085 return nil
1086 }
1087
1088
1089 func (f *File) DropGoStmt() {
1090 if f.Go != nil {
1091 f.Go.Syntax.markRemoved()
1092 f.Go = nil
1093 }
1094 }
1095
1096
1097 func (f *File) DropToolchainStmt() {
1098 if f.Toolchain != nil {
1099 f.Toolchain.Syntax.markRemoved()
1100 f.Toolchain = nil
1101 }
1102 }
1103
1104 func (f *File) AddToolchainStmt(name string) error {
1105 if !ToolchainRE.MatchString(name) {
1106 return fmt.Errorf("invalid toolchain name %q", name)
1107 }
1108 if f.Toolchain == nil {
1109 var hint Expr
1110 if f.Go != nil && f.Go.Syntax != nil {
1111 hint = f.Go.Syntax
1112 } else if f.Module != nil && f.Module.Syntax != nil {
1113 hint = f.Module.Syntax
1114 }
1115 f.Toolchain = &Toolchain{
1116 Name: name,
1117 Syntax: f.Syntax.addLine(hint, "toolchain", name),
1118 }
1119 } else {
1120 f.Toolchain.Name = name
1121 f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
1122 }
1123 return nil
1124 }
1125
1126
1127
1128
1129
1130
1131
1132 func (f *File) AddGodebug(key, value string) error {
1133 need := true
1134 for _, g := range f.Godebug {
1135 if g.Key == key {
1136 if need {
1137 g.Value = value
1138 f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value)
1139 need = false
1140 } else {
1141 g.Syntax.markRemoved()
1142 *g = Godebug{}
1143 }
1144 }
1145 }
1146
1147 if need {
1148 f.addNewGodebug(key, value)
1149 }
1150 return nil
1151 }
1152
1153
1154
1155 func (f *File) addNewGodebug(key, value string) {
1156 line := f.Syntax.addLine(nil, "godebug", key+"="+value)
1157 g := &Godebug{
1158 Key: key,
1159 Value: value,
1160 Syntax: line,
1161 }
1162 f.Godebug = append(f.Godebug, g)
1163 }
1164
1165
1166
1167
1168
1169
1170
1171 func (f *File) AddRequire(path, vers string) error {
1172 need := true
1173 for _, r := range f.Require {
1174 if r.Mod.Path == path {
1175 if need {
1176 r.Mod.Version = vers
1177 f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
1178 need = false
1179 } else {
1180 r.Syntax.markRemoved()
1181 *r = Require{}
1182 }
1183 }
1184 }
1185
1186 if need {
1187 f.AddNewRequire(path, vers, false)
1188 }
1189 return nil
1190 }
1191
1192
1193
1194 func (f *File) AddNewRequire(path, vers string, indirect bool) {
1195 line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
1196 r := &Require{
1197 Mod: module.Version{Path: path, Version: vers},
1198 Syntax: line,
1199 }
1200 r.setIndirect(indirect)
1201 f.Require = append(f.Require, r)
1202 }
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218 func (f *File) SetRequire(req []*Require) {
1219 type elem struct {
1220 version string
1221 indirect bool
1222 }
1223 need := make(map[string]elem)
1224 for _, r := range req {
1225 if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version {
1226 panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version))
1227 }
1228 need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect}
1229 }
1230
1231
1232
1233 for _, r := range f.Require {
1234 e, ok := need[r.Mod.Path]
1235 if ok {
1236 r.setVersion(e.version)
1237 r.setIndirect(e.indirect)
1238 } else {
1239 r.markRemoved()
1240 }
1241 delete(need, r.Mod.Path)
1242 }
1243
1244
1245
1246
1247
1248
1249 for path, e := range need {
1250 f.AddNewRequire(path, e.version, e.indirect)
1251 }
1252
1253 f.SortBlocks()
1254 }
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274 func (f *File) SetRequireSeparateIndirect(req []*Require) {
1275
1276
1277 hasComments := func(c Comments) bool {
1278 return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 ||
1279 (len(c.Suffix) == 1 &&
1280 strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect")
1281 }
1282
1283
1284
1285 moveReq := func(r *Require, block *LineBlock) {
1286 var line *Line
1287 if r.Syntax == nil {
1288 line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}}
1289 r.Syntax = line
1290 if r.Indirect {
1291 r.setIndirect(true)
1292 }
1293 } else {
1294 line = new(Line)
1295 *line = *r.Syntax
1296 if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" {
1297 line.Token = line.Token[1:]
1298 }
1299 r.Syntax.Token = nil
1300 r.Syntax = line
1301 }
1302 line.InBlock = true
1303 block.Line = append(block.Line, line)
1304 }
1305
1306
1307 var (
1308
1309
1310
1311 lastDirectIndex = -1
1312 lastIndirectIndex = -1
1313
1314
1315
1316 lastRequireIndex = -1
1317
1318
1319
1320 requireLineOrBlockCount = 0
1321
1322
1323
1324 lineToBlock = make(map[*Line]*LineBlock)
1325 )
1326 for i, stmt := range f.Syntax.Stmt {
1327 switch stmt := stmt.(type) {
1328 case *Line:
1329 if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
1330 continue
1331 }
1332 lastRequireIndex = i
1333 requireLineOrBlockCount++
1334 if !hasComments(stmt.Comments) {
1335 if isIndirect(stmt) {
1336 lastIndirectIndex = i
1337 } else {
1338 lastDirectIndex = i
1339 }
1340 }
1341
1342 case *LineBlock:
1343 if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
1344 continue
1345 }
1346 lastRequireIndex = i
1347 requireLineOrBlockCount++
1348 allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
1349 allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
1350 for _, line := range stmt.Line {
1351 lineToBlock[line] = stmt
1352 if hasComments(line.Comments) {
1353 allDirect = false
1354 allIndirect = false
1355 } else if isIndirect(line) {
1356 allDirect = false
1357 } else {
1358 allIndirect = false
1359 }
1360 }
1361 if allDirect {
1362 lastDirectIndex = i
1363 }
1364 if allIndirect {
1365 lastIndirectIndex = i
1366 }
1367 }
1368 }
1369
1370 oneFlatUncommentedBlock := requireLineOrBlockCount == 1 &&
1371 !hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment())
1372
1373
1374
1375
1376 insertBlock := func(i int) *LineBlock {
1377 block := &LineBlock{Token: []string{"require"}}
1378 f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
1379 copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
1380 f.Syntax.Stmt[i] = block
1381 return block
1382 }
1383
1384 ensureBlock := func(i int) *LineBlock {
1385 switch stmt := f.Syntax.Stmt[i].(type) {
1386 case *LineBlock:
1387 return stmt
1388 case *Line:
1389 block := &LineBlock{
1390 Token: []string{"require"},
1391 Line: []*Line{stmt},
1392 }
1393 stmt.Token = stmt.Token[1:]
1394 stmt.InBlock = true
1395 f.Syntax.Stmt[i] = block
1396 return block
1397 default:
1398 panic(fmt.Sprintf("unexpected statement: %v", stmt))
1399 }
1400 }
1401
1402 var lastDirectBlock *LineBlock
1403 if lastDirectIndex < 0 {
1404 if lastIndirectIndex >= 0 {
1405 lastDirectIndex = lastIndirectIndex
1406 lastIndirectIndex++
1407 } else if lastRequireIndex >= 0 {
1408 lastDirectIndex = lastRequireIndex + 1
1409 } else {
1410 lastDirectIndex = len(f.Syntax.Stmt)
1411 }
1412 lastDirectBlock = insertBlock(lastDirectIndex)
1413 } else {
1414 lastDirectBlock = ensureBlock(lastDirectIndex)
1415 }
1416
1417 var lastIndirectBlock *LineBlock
1418 if lastIndirectIndex < 0 {
1419 lastIndirectIndex = lastDirectIndex + 1
1420 lastIndirectBlock = insertBlock(lastIndirectIndex)
1421 } else {
1422 lastIndirectBlock = ensureBlock(lastIndirectIndex)
1423 }
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433 need := make(map[string]*Require)
1434 for _, r := range req {
1435 need[r.Mod.Path] = r
1436 }
1437 have := make(map[string]*Require)
1438 for _, r := range f.Require {
1439 path := r.Mod.Path
1440 if need[path] == nil || have[path] != nil {
1441
1442 r.markRemoved()
1443 continue
1444 }
1445 have[r.Mod.Path] = r
1446 r.setVersion(need[path].Mod.Version)
1447 r.setIndirect(need[path].Indirect)
1448 if need[path].Indirect &&
1449 (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
1450 moveReq(r, lastIndirectBlock)
1451 } else if !need[path].Indirect &&
1452 (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
1453 moveReq(r, lastDirectBlock)
1454 }
1455 }
1456
1457
1458 for path, r := range need {
1459 if have[path] == nil {
1460 if r.Indirect {
1461 moveReq(r, lastIndirectBlock)
1462 } else {
1463 moveReq(r, lastDirectBlock)
1464 }
1465 f.Require = append(f.Require, r)
1466 }
1467 }
1468
1469 f.SortBlocks()
1470 }
1471
1472 func (f *File) DropGodebug(key string) error {
1473 for _, g := range f.Godebug {
1474 if g.Key == key {
1475 g.Syntax.markRemoved()
1476 *g = Godebug{}
1477 }
1478 }
1479 return nil
1480 }
1481
1482 func (f *File) DropRequire(path string) error {
1483 for _, r := range f.Require {
1484 if r.Mod.Path == path {
1485 r.Syntax.markRemoved()
1486 *r = Require{}
1487 }
1488 }
1489 return nil
1490 }
1491
1492
1493
1494 func (f *File) AddExclude(path, vers string) error {
1495 if err := checkCanonicalVersion(path, vers); err != nil {
1496 return err
1497 }
1498
1499 var hint *Line
1500 for _, x := range f.Exclude {
1501 if x.Mod.Path == path && x.Mod.Version == vers {
1502 return nil
1503 }
1504 if x.Mod.Path == path {
1505 hint = x.Syntax
1506 }
1507 }
1508
1509 f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
1510 return nil
1511 }
1512
1513 func (f *File) DropExclude(path, vers string) error {
1514 for _, x := range f.Exclude {
1515 if x.Mod.Path == path && x.Mod.Version == vers {
1516 x.Syntax.markRemoved()
1517 *x = Exclude{}
1518 }
1519 }
1520 return nil
1521 }
1522
1523 func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
1524 return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
1525 }
1526
1527 func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
1528 need := true
1529 old := module.Version{Path: oldPath, Version: oldVers}
1530 new := module.Version{Path: newPath, Version: newVers}
1531 tokens := []string{"replace", AutoQuote(oldPath)}
1532 if oldVers != "" {
1533 tokens = append(tokens, oldVers)
1534 }
1535 tokens = append(tokens, "=>", AutoQuote(newPath))
1536 if newVers != "" {
1537 tokens = append(tokens, newVers)
1538 }
1539
1540 var hint *Line
1541 for _, r := range *replace {
1542 if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
1543 if need {
1544
1545 r.New = new
1546 syntax.updateLine(r.Syntax, tokens...)
1547 need = false
1548 continue
1549 }
1550
1551 r.Syntax.markRemoved()
1552 *r = Replace{}
1553 }
1554 if r.Old.Path == oldPath {
1555 hint = r.Syntax
1556 }
1557 }
1558 if need {
1559 *replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
1560 }
1561 return nil
1562 }
1563
1564 func (f *File) DropReplace(oldPath, oldVers string) error {
1565 for _, r := range f.Replace {
1566 if r.Old.Path == oldPath && r.Old.Version == oldVers {
1567 r.Syntax.markRemoved()
1568 *r = Replace{}
1569 }
1570 }
1571 return nil
1572 }
1573
1574
1575
1576 func (f *File) AddRetract(vi VersionInterval, rationale string) error {
1577 var path string
1578 if f.Module != nil {
1579 path = f.Module.Mod.Path
1580 }
1581 if err := checkCanonicalVersion(path, vi.High); err != nil {
1582 return err
1583 }
1584 if err := checkCanonicalVersion(path, vi.Low); err != nil {
1585 return err
1586 }
1587
1588 r := &Retract{
1589 VersionInterval: vi,
1590 }
1591 if vi.Low == vi.High {
1592 r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
1593 } else {
1594 r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
1595 }
1596 if rationale != "" {
1597 for _, line := range strings.Split(rationale, "\n") {
1598 com := Comment{Token: "// " + line}
1599 r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
1600 }
1601 }
1602 return nil
1603 }
1604
1605 func (f *File) DropRetract(vi VersionInterval) error {
1606 for _, r := range f.Retract {
1607 if r.VersionInterval == vi {
1608 r.Syntax.markRemoved()
1609 *r = Retract{}
1610 }
1611 }
1612 return nil
1613 }
1614
1615
1616
1617 func (f *File) AddTool(path string) error {
1618 for _, t := range f.Tool {
1619 if t.Path == path {
1620 return nil
1621 }
1622 }
1623
1624 f.Tool = append(f.Tool, &Tool{
1625 Path: path,
1626 Syntax: f.Syntax.addLine(nil, "tool", path),
1627 })
1628
1629 f.SortBlocks()
1630 return nil
1631 }
1632
1633
1634
1635 func (f *File) DropTool(path string) error {
1636 for _, t := range f.Tool {
1637 if t.Path == path {
1638 t.Syntax.markRemoved()
1639 *t = Tool{}
1640 }
1641 }
1642 return nil
1643 }
1644
1645
1646
1647 func (f *File) AddIgnore(path string) error {
1648 for _, t := range f.Ignore {
1649 if t.Path == path {
1650 return nil
1651 }
1652 }
1653
1654 f.Ignore = append(f.Ignore, &Ignore{
1655 Path: path,
1656 Syntax: f.Syntax.addLine(nil, "ignore", path),
1657 })
1658
1659 f.SortBlocks()
1660 return nil
1661 }
1662
1663
1664
1665 func (f *File) DropIgnore(path string) error {
1666 for _, t := range f.Ignore {
1667 if t.Path == path {
1668 t.Syntax.markRemoved()
1669 *t = Ignore{}
1670 }
1671 }
1672 return nil
1673 }
1674
1675 func (f *File) SortBlocks() {
1676 f.removeDups()
1677
1678
1679
1680
1681 const semanticSortForExcludeVersionV = "v1.21"
1682 useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0
1683
1684 for _, stmt := range f.Syntax.Stmt {
1685 block, ok := stmt.(*LineBlock)
1686 if !ok {
1687 continue
1688 }
1689 less := compareLine
1690 if block.Token[0] == "exclude" && useSemanticSortForExclude {
1691 less = compareLineExclude
1692 } else if block.Token[0] == "retract" {
1693 less = compareLineRetract
1694 }
1695 slices.SortStableFunc(block.Line, less)
1696 }
1697 }
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710 func (f *File) removeDups() {
1711 removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool, &f.Ignore)
1712 }
1713
1714 func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool, ignore *[]*Ignore) {
1715 kill := make(map[*Line]bool)
1716
1717
1718 if exclude != nil {
1719 haveExclude := make(map[module.Version]bool)
1720 for _, x := range *exclude {
1721 if haveExclude[x.Mod] {
1722 kill[x.Syntax] = true
1723 continue
1724 }
1725 haveExclude[x.Mod] = true
1726 }
1727 var excl []*Exclude
1728 for _, x := range *exclude {
1729 if !kill[x.Syntax] {
1730 excl = append(excl, x)
1731 }
1732 }
1733 *exclude = excl
1734 }
1735
1736
1737
1738 haveReplace := make(map[module.Version]bool)
1739 for i := len(*replace) - 1; i >= 0; i-- {
1740 x := (*replace)[i]
1741 if haveReplace[x.Old] {
1742 kill[x.Syntax] = true
1743 continue
1744 }
1745 haveReplace[x.Old] = true
1746 }
1747 var repl []*Replace
1748 for _, x := range *replace {
1749 if !kill[x.Syntax] {
1750 repl = append(repl, x)
1751 }
1752 }
1753 *replace = repl
1754
1755 if tool != nil {
1756 haveTool := make(map[string]bool)
1757 for _, t := range *tool {
1758 if haveTool[t.Path] {
1759 kill[t.Syntax] = true
1760 continue
1761 }
1762 haveTool[t.Path] = true
1763 }
1764 var newTool []*Tool
1765 for _, t := range *tool {
1766 if !kill[t.Syntax] {
1767 newTool = append(newTool, t)
1768 }
1769 }
1770 *tool = newTool
1771 }
1772
1773 if ignore != nil {
1774 haveIgnore := make(map[string]bool)
1775 for _, i := range *ignore {
1776 if haveIgnore[i.Path] {
1777 kill[i.Syntax] = true
1778 continue
1779 }
1780 haveIgnore[i.Path] = true
1781 }
1782 var newIgnore []*Ignore
1783 for _, i := range *ignore {
1784 if !kill[i.Syntax] {
1785 newIgnore = append(newIgnore, i)
1786 }
1787 }
1788 *ignore = newIgnore
1789 }
1790
1791
1792
1793
1794 var stmts []Expr
1795 for _, stmt := range syntax.Stmt {
1796 switch stmt := stmt.(type) {
1797 case *Line:
1798 if kill[stmt] {
1799 continue
1800 }
1801 case *LineBlock:
1802 var lines []*Line
1803 for _, line := range stmt.Line {
1804 if !kill[line] {
1805 lines = append(lines, line)
1806 }
1807 }
1808 stmt.Line = lines
1809 if len(lines) == 0 {
1810 continue
1811 }
1812 }
1813 stmts = append(stmts, stmt)
1814 }
1815 syntax.Stmt = stmts
1816 }
1817
1818
1819
1820 func compareLine(li, lj *Line) int {
1821 for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
1822 if li.Token[k] != lj.Token[k] {
1823 return cmp.Compare(li.Token[k], lj.Token[k])
1824 }
1825 }
1826 return cmp.Compare(len(li.Token), len(lj.Token))
1827 }
1828
1829
1830 func compareLineExclude(li, lj *Line) int {
1831 if len(li.Token) != 2 || len(lj.Token) != 2 {
1832
1833
1834 return compareLine(li, lj)
1835 }
1836
1837
1838 if pi, pj := li.Token[0], lj.Token[0]; pi != pj {
1839 return cmp.Compare(pi, pj)
1840 }
1841 return semver.Compare(li.Token[1], lj.Token[1])
1842 }
1843
1844
1845
1846
1847
1848
1849 func compareLineRetract(li, lj *Line) int {
1850 interval := func(l *Line) VersionInterval {
1851 if len(l.Token) == 1 {
1852 return VersionInterval{Low: l.Token[0], High: l.Token[0]}
1853 } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
1854 return VersionInterval{Low: l.Token[1], High: l.Token[3]}
1855 } else {
1856
1857 return VersionInterval{}
1858 }
1859 }
1860 vii := interval(li)
1861 vij := interval(lj)
1862 if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
1863 return -cmp
1864 }
1865 return -semver.Compare(vii.High, vij.High)
1866 }
1867
1868
1869
1870
1871
1872
1873 func checkCanonicalVersion(path, vers string) error {
1874 _, pathMajor, pathMajorOk := module.SplitPathVersion(path)
1875
1876 if vers == "" || vers != module.CanonicalVersion(vers) {
1877 if pathMajor == "" {
1878 return &module.InvalidVersionError{
1879 Version: vers,
1880 Err: fmt.Errorf("must be of the form v1.2.3"),
1881 }
1882 }
1883 return &module.InvalidVersionError{
1884 Version: vers,
1885 Err: fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)),
1886 }
1887 }
1888
1889 if pathMajorOk {
1890 if err := module.CheckPathMajor(vers, pathMajor); err != nil {
1891 if pathMajor == "" {
1892
1893
1894 return &module.InvalidVersionError{
1895 Version: vers,
1896 Err: fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)),
1897 }
1898 }
1899 return err
1900 }
1901 }
1902
1903 return nil
1904 }
1905
View as plain text