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