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