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