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