Skip to content

Commit 66f6d92

Browse files
author
Bryan C. Mills
committed
modfile: check canonicalness against the relevant module path, not abstract semver
In CL 279394 we started validating that versions in "exclude" and "retract" directives are canonical. Unfortunately, we use the semver package's notion of canonicalness, and the semver package doesn't know anything about +incompatible versions or major-version suffixes. The resulting error messages also don't indicate an appropriate fix if the problem is that the user forgot either the "+incompatible" suffix on the version string or the "/vN" suffix on the module path. This change corrects both of those problems by validating the version against the corresponding module path. (For "exclude" directives, that is the module path to be excluded; for "retract" directives, it is the module declared in the "module" directive of the same go.mod file.) For golang/go#44497 Change-Id: I39732d79c3ab3a43bb1fb8905062fe6cb26d3edc Reviewed-on: https://go-review.googlesource.com/c/mod/+/295089 Trust: Bryan C. Mills <[email protected]> Reviewed-by: Jay Conrod <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 6ce8bb3 commit 66f6d92

File tree

2 files changed

+183
-48
lines changed

2 files changed

+183
-48
lines changed

modfile/rule.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -835,11 +835,8 @@ func (f *File) DropRequire(path string) error {
835835
// AddExclude adds a exclude statement to the mod file. Errors if the provided
836836
// version is not a canonical version string
837837
func (f *File) AddExclude(path, vers string) error {
838-
if !isCanonicalVersion(vers) {
839-
return &module.InvalidVersionError{
840-
Version: vers,
841-
Err: errors.New("must be of the form v1.2.3"),
842-
}
838+
if err := checkCanonicalVersion(path, vers); err != nil {
839+
return err
843840
}
844841

845842
var hint *Line
@@ -916,17 +913,15 @@ func (f *File) DropReplace(oldPath, oldVers string) error {
916913
// AddRetract adds a retract statement to the mod file. Errors if the provided
917914
// version interval does not consist of canonical version strings
918915
func (f *File) AddRetract(vi VersionInterval, rationale string) error {
919-
if !isCanonicalVersion(vi.High) {
920-
return &module.InvalidVersionError{
921-
Version: vi.High,
922-
Err: errors.New("must be of the form v1.2.3"),
923-
}
916+
var path string
917+
if f.Module != nil {
918+
path = f.Module.Mod.Path
924919
}
925-
if !isCanonicalVersion(vi.Low) {
926-
return &module.InvalidVersionError{
927-
Version: vi.Low,
928-
Err: errors.New("must be of the form v1.2.3"),
929-
}
920+
if err := checkCanonicalVersion(path, vi.High); err != nil {
921+
return err
922+
}
923+
if err := checkCanonicalVersion(path, vi.Low); err != nil {
924+
return err
930925
}
931926

932927
r := &Retract{
@@ -1086,8 +1081,40 @@ func lineRetractLess(li, lj *Line) bool {
10861081
return semver.Compare(vii.High, vij.High) > 0
10871082
}
10881083

1089-
// isCanonicalVersion tests if the provided version string represents a valid
1090-
// canonical version.
1091-
func isCanonicalVersion(vers string) bool {
1092-
return vers != "" && semver.Canonical(vers) == vers
1084+
// checkCanonicalVersion returns a non-nil error if vers is not a canonical
1085+
// version string or does not match the major version of path.
1086+
//
1087+
// If path is non-empty, the error text suggests a format with a major version
1088+
// corresponding to the path.
1089+
func checkCanonicalVersion(path, vers string) error {
1090+
_, pathMajor, pathMajorOk := module.SplitPathVersion(path)
1091+
1092+
if vers == "" || vers != module.CanonicalVersion(vers) {
1093+
if pathMajor == "" {
1094+
return &module.InvalidVersionError{
1095+
Version: vers,
1096+
Err: fmt.Errorf("must be of the form v1.2.3"),
1097+
}
1098+
}
1099+
return &module.InvalidVersionError{
1100+
Version: vers,
1101+
Err: fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)),
1102+
}
1103+
}
1104+
1105+
if pathMajorOk {
1106+
if err := module.CheckPathMajor(vers, pathMajor); err != nil {
1107+
if pathMajor == "" {
1108+
// In this context, the user probably wrote "v2.3.4" when they meant
1109+
// "v2.3.4+incompatible". Suggest that instead of "v0 or v1".
1110+
return &module.InvalidVersionError{
1111+
Version: vers,
1112+
Err: fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)),
1113+
}
1114+
}
1115+
return err
1116+
}
1117+
}
1118+
1119+
return nil
10931120
}

modfile/rule_test.go

Lines changed: 137 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package modfile
66

77
import (
88
"bytes"
9+
"fmt"
910
"testing"
1011

1112
"golang.org/x/mod/module"
@@ -189,6 +190,45 @@ var addGoTests = []struct {
189190
},
190191
}
191192

193+
var addExcludeTests = []struct {
194+
desc string
195+
in string
196+
path string
197+
version string
198+
out string
199+
}{
200+
{
201+
`compatible`,
202+
`module m
203+
`,
204+
`example.com`,
205+
`v1.2.3`,
206+
`module m
207+
exclude example.com v1.2.3
208+
`,
209+
},
210+
{
211+
`gopkg.in v0`,
212+
`module m
213+
`,
214+
`gopkg.in/foo.v0`,
215+
`v0.2.3`,
216+
`module m
217+
exclude gopkg.in/foo.v0 v0.2.3
218+
`,
219+
},
220+
{
221+
`gopkg.in v1`,
222+
`module m
223+
`,
224+
`gopkg.in/foo.v1`,
225+
`v1.2.3`,
226+
`module m
227+
exclude gopkg.in/foo.v1 v1.2.3
228+
`,
229+
},
230+
}
231+
192232
var addRetractTests = []struct {
193233
desc string
194234
in string
@@ -569,44 +609,90 @@ var sortBlocksTests = []struct {
569609
}
570610

571611
var addRetractValidateVersionTests = []struct {
572-
dsc, low, high string
612+
desc string
613+
path string
614+
low, high string
615+
wantErr string
573616
}{
574617
{
575-
"blank_version",
576-
"",
577-
"",
618+
`blank_version`,
619+
`example.com/m`,
620+
``,
621+
``,
622+
`version "" invalid: must be of the form v1.2.3`,
623+
},
624+
{
625+
`missing prefix`,
626+
`example.com/m`,
627+
`1.0.0`,
628+
`1.0.0`,
629+
`version "1.0.0" invalid: must be of the form v1.2.3`,
630+
},
631+
{
632+
`non-canonical`,
633+
`example.com/m`,
634+
`v1.2`,
635+
`v1.2`,
636+
`version "v1.2" invalid: must be of the form v1.2.3`,
578637
},
579638
{
580-
"missing_prefix",
581-
"1.0.0",
582-
"1.0.0",
639+
`invalid range`,
640+
`example.com/m`,
641+
`v1.2.3`,
642+
`v1.3`,
643+
`version "v1.3" invalid: must be of the form v1.2.3`,
583644
},
584645
{
585-
"non_canonical",
586-
"v1.2",
587-
"v1.2",
646+
`mismatched major`,
647+
`example.com/m/v2`,
648+
`v1.0.0`,
649+
`v1.0.0`,
650+
`version "v1.0.0" invalid: should be v2, not v1`,
588651
},
589652
{
590-
"invalid_range",
591-
"v1.2.3",
592-
"v1.3",
653+
`missing +incompatible`,
654+
`example.com/m`,
655+
`v2.0.0`,
656+
`v2.0.0`,
657+
`version "v2.0.0" invalid: should be v2.0.0+incompatible (or module example.com/m/v2)`,
593658
},
594659
}
595660

596661
var addExcludeValidateVersionTests = []struct {
597-
dsc, ver string
662+
desc string
663+
path string
664+
version string
665+
wantErr string
598666
}{
599667
{
600-
"blank_version",
601-
"",
668+
`blank version`,
669+
`example.com/m`,
670+
``,
671+
`version "" invalid: must be of the form v1.2.3`,
672+
},
673+
{
674+
`missing prefix`,
675+
`example.com/m`,
676+
`1.0.0`,
677+
`version "1.0.0" invalid: must be of the form v1.2.3`,
602678
},
603679
{
604-
"missing_prefix",
605-
"1.0.0",
680+
`non-canonical`,
681+
`example.com/m`,
682+
`v1.2`,
683+
`version "v1.2" invalid: must be of the form v1.2.3`,
684+
},
685+
{
686+
`mismatched major`,
687+
`example.com/m/v2`,
688+
`v1.2.3`,
689+
`version "v1.2.3" invalid: should be v2, not v1`,
606690
},
607691
{
608-
"non_canonical",
609-
"v1.2",
692+
`missing +incompatible`,
693+
`example.com/m`,
694+
`v2.3.4`,
695+
`version "v2.3.4" invalid: should be v2.3.4+incompatible (or module example.com/m/v2)`,
610696
},
611697
}
612698

@@ -657,6 +743,16 @@ func TestAddGo(t *testing.T) {
657743
}
658744
}
659745

746+
func TestAddExclude(t *testing.T) {
747+
for _, tt := range addExcludeTests {
748+
t.Run(tt.desc, func(t *testing.T) {
749+
testEdit(t, tt.in, tt.out, true, func(f *File) error {
750+
return f.AddExclude(tt.path, tt.version)
751+
})
752+
})
753+
}
754+
}
755+
660756
func TestAddRetract(t *testing.T) {
661757
for _, tt := range addRetractTests {
662758
t.Run(tt.desc, func(t *testing.T) {
@@ -744,27 +840,39 @@ func testEdit(t *testing.T, in, want string, strict bool, transform func(f *File
744840

745841
func TestAddRetractValidateVersion(t *testing.T) {
746842
for _, tt := range addRetractValidateVersionTests {
747-
t.Run(tt.dsc, func(t *testing.T) {
748-
f, err := Parse("in", []byte("module m"), nil)
749-
if err != nil {
750-
t.Fatal(err)
843+
t.Run(tt.desc, func(t *testing.T) {
844+
f := new(File)
845+
if tt.path != "" {
846+
if err := f.AddModuleStmt(tt.path); err != nil {
847+
t.Fatal(err)
848+
}
849+
t.Logf("module %s", AutoQuote(tt.path))
751850
}
752-
if err = f.AddRetract(VersionInterval{Low: tt.low, High: tt.high}, ""); err == nil {
753-
t.Fatal("expected AddRetract to complain about version format")
851+
interval := VersionInterval{Low: tt.low, High: tt.high}
852+
if err := f.AddRetract(interval, ``); err == nil || err.Error() != tt.wantErr {
853+
errStr := "<nil>"
854+
if err != nil {
855+
errStr = fmt.Sprintf("%#q", err)
856+
}
857+
t.Fatalf("f.AddRetract(%+v, ``) = %s\nwant %#q", interval, errStr, tt.wantErr)
754858
}
755859
})
756860
}
757861
}
758862

759863
func TestAddExcludeValidateVersion(t *testing.T) {
760864
for _, tt := range addExcludeValidateVersionTests {
761-
t.Run(tt.dsc, func(t *testing.T) {
865+
t.Run(tt.desc, func(t *testing.T) {
762866
f, err := Parse("in", []byte("module m"), nil)
763867
if err != nil {
764868
t.Fatal(err)
765869
}
766-
if err = f.AddExclude("aa", tt.ver); err == nil {
767-
t.Fatal("expected AddExclude to complain about version format")
870+
if err = f.AddExclude(tt.path, tt.version); err == nil || err.Error() != tt.wantErr {
871+
errStr := "<nil>"
872+
if err != nil {
873+
errStr = fmt.Sprintf("%#q", err)
874+
}
875+
t.Fatalf("f.AddExclude(%q, %q) = %s\nwant %#q", tt.path, tt.version, errStr, tt.wantErr)
768876
}
769877
})
770878
}

0 commit comments

Comments
 (0)