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