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