Skip to content

Commit 6e4e729

Browse files
author
Bryan C. Mills
committed
modfile: clean up SetRequire
I started this change by expanding the documentation and tests for SetRequire. Unfortunately, the tests failed when the existing contents included duplicates of a module path: --- FAIL: TestSetRequire/existing_duplicate (0.00s) rule_test.go:1011: after Cleanup, len(Require) = 3; want 1 --- FAIL: TestSetRequire/existing_duplicate_multi (0.00s) rule_test.go:1011: after Cleanup, len(Require) = 3; want 1 So then I fixed the detected bug, by updating the Line entries (possibly marking them for removal) in the same loop that updates the Require entries. (We don't need to loop over f.Syntax.Stmt separately to remove deleted entries because f.Syntax.Cleanup already does that.) For golang/go#45965 Change-Id: I1b665c0832112de2c4273628f266dc3d966fefdd Reviewed-on: https://go-review.googlesource.com/c/mod/+/325230 Trust: Bryan C. Mills <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent ad21a28 commit 6e4e729

File tree

2 files changed

+129
-61
lines changed

2 files changed

+129
-61
lines changed

modfile/rule.go

+48-39
Original file line numberDiff line numberDiff line change
@@ -870,67 +870,76 @@ func (f *File) AddNewRequire(path, vers string, indirect bool) {
870870
f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line})
871871
}
872872

873-
// SetRequire sets the requirements of f to contain exactly req, preserving
874-
// any existing line comments contents (except for 'indirect' markings)
875-
// for the module versions named in req.
873+
// SetRequire updates the requirements of f to contain exactly req, preserving
874+
// the existing block structure and line comment contents (except for 'indirect'
875+
// markings) for the first requirement on each named module path.
876+
//
877+
// The Syntax field is ignored for the requirements in req.
878+
//
879+
// Any requirements not already present in the file are added to the block
880+
// containing the last require line.
881+
//
882+
// The requirements in req must specify at most one distinct version for each
883+
// module path.
884+
//
885+
// If any existing requirements may be removed, the caller should call Cleanup
886+
// after all edits are complete.
876887
func (f *File) SetRequire(req []*Require) {
877888
need := make(map[string]string)
878889
indirect := make(map[string]bool)
879890
for _, r := range req {
891+
if prev, dup := need[r.Mod.Path]; dup && prev != r.Mod.Version {
892+
panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev, r.Mod.Version))
893+
}
880894
need[r.Mod.Path] = r.Mod.Version
881895
indirect[r.Mod.Path] = r.Indirect
882896
}
883897

898+
// Update or delete the existing Require entries to preserve
899+
// only the first for each module path in req.
884900
for _, r := range f.Require {
885-
if v, ok := need[r.Mod.Path]; ok {
886-
r.Mod.Version = v
887-
r.Indirect = indirect[r.Mod.Path]
888-
} else {
901+
v, ok := need[r.Mod.Path]
902+
if !ok {
903+
// This line is redundant or its path is no longer required at all.
904+
// Mark the requirement for deletion in Cleanup.
905+
r.Syntax.Token = nil
889906
*r = Require{}
890907
}
891-
}
892908

893-
var newStmts []Expr
894-
for _, stmt := range f.Syntax.Stmt {
895-
switch stmt := stmt.(type) {
896-
case *LineBlock:
897-
if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
898-
var newLines []*Line
899-
for _, line := range stmt.Line {
900-
if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" {
901-
if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
902-
line.Comments.Before = line.Comments.Before[:0]
903-
}
904-
line.Token[1] = need[p]
905-
delete(need, p)
906-
setIndirect(line, indirect[p])
907-
newLines = append(newLines, line)
908-
}
909+
r.Mod.Version = v
910+
r.Indirect = indirect[r.Mod.Path]
911+
912+
if line := r.Syntax; line != nil && len(line.Token) > 0 {
913+
if line.InBlock {
914+
// If the line is preceded by an empty line, remove it; see
915+
// https://golang.org/issue/33779.
916+
if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
917+
line.Comments.Before = line.Comments.Before[:0]
909918
}
910-
if len(newLines) == 0 {
911-
continue // drop stmt
919+
if len(line.Token) >= 2 { // example.com v1.2.3
920+
line.Token[1] = v
912921
}
913-
stmt.Line = newLines
914-
}
915-
916-
case *Line:
917-
if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
918-
if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" {
919-
stmt.Token[2] = need[p]
920-
delete(need, p)
921-
setIndirect(stmt, indirect[p])
922-
} else {
923-
continue // drop stmt
922+
} else {
923+
if len(line.Token) >= 3 { // require example.com v1.2.3
924+
line.Token[2] = v
924925
}
925926
}
927+
928+
setIndirect(line, r.Indirect)
926929
}
927-
newStmts = append(newStmts, stmt)
930+
931+
delete(need, r.Mod.Path)
928932
}
929-
f.Syntax.Stmt = newStmts
930933

934+
// Add new entries in the last block of the file for any paths that weren't
935+
// already present.
936+
//
937+
// This step is nondeterministic, but the final result will be deterministic
938+
// because we will sort the block.
931939
for path, vers := range need {
932940
f.AddNewRequire(path, vers, indirect[path])
933941
}
942+
934943
f.SortBlocks()
935944
}
936945

modfile/rule_test.go

+81-22
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,16 @@ var addRequireTests = []struct {
9292
},
9393
}
9494

95+
type require struct {
96+
path, vers string
97+
indirect bool
98+
}
99+
95100
var setRequireTests = []struct {
96101
desc string
97102
in string
98-
mods []struct {
99-
path string
100-
vers string
101-
indirect bool
102-
}
103-
out string
103+
mods []require
104+
out string
104105
}{
105106
{
106107
`https://golang.org/issue/45932`,
@@ -111,11 +112,7 @@ var setRequireTests = []struct {
111112
x.y/c v1.2.3
112113
)
113114
`,
114-
[]struct {
115-
path string
116-
vers string
117-
indirect bool
118-
}{
115+
[]require{
119116
{"x.y/a", "v1.2.3", false},
120117
{"x.y/b", "v1.2.3", false},
121118
{"x.y/c", "v1.2.3", false},
@@ -138,11 +135,7 @@ var setRequireTests = []struct {
138135
x.y/d v1.2.3
139136
)
140137
`,
141-
[]struct {
142-
path string
143-
vers string
144-
indirect bool
145-
}{
138+
[]require{
146139
{"x.y/a", "v1.2.3", false},
147140
{"x.y/b", "v1.2.3", false},
148141
{"x.y/c", "v1.2.3", false},
@@ -168,11 +161,7 @@ var setRequireTests = []struct {
168161
x.y/g v1.2.3 // indirect
169162
)
170163
`,
171-
[]struct {
172-
path string
173-
vers string
174-
indirect bool
175-
}{
164+
[]require{
176165
{"x.y/a", "v1.2.3", true},
177166
{"x.y/b", "v1.2.3", true},
178167
{"x.y/c", "v1.2.3", true},
@@ -193,6 +182,76 @@ var setRequireTests = []struct {
193182
)
194183
`,
195184
},
185+
{
186+
`existing_multi`,
187+
`module m
188+
require x.y/a v1.2.3
189+
require x.y/b v1.2.3
190+
require x.y/c v1.0.0 // not v1.2.3!
191+
require x.y/d v1.2.3 // comment kept
192+
require x.y/e v1.2.3 // comment kept
193+
require x.y/f v1.2.3 // indirect
194+
require x.y/g v1.2.3 // indirect
195+
`,
196+
[]require{
197+
{"x.y/h", "v1.2.3", false},
198+
{"x.y/a", "v1.2.3", false},
199+
{"x.y/b", "v1.2.3", false},
200+
{"x.y/c", "v1.2.3", false},
201+
{"x.y/d", "v1.2.3", false},
202+
{"x.y/e", "v1.2.3", true},
203+
{"x.y/f", "v1.2.3", false},
204+
{"x.y/g", "v1.2.3", false},
205+
},
206+
`module m
207+
require x.y/a v1.2.3
208+
209+
require x.y/b v1.2.3
210+
211+
require x.y/c v1.2.3 // not v1.2.3!
212+
213+
require x.y/d v1.2.3 // comment kept
214+
215+
require x.y/e v1.2.3 // indirect; comment kept
216+
217+
require x.y/f v1.2.3
218+
219+
require (
220+
x.y/g v1.2.3
221+
x.y/h v1.2.3
222+
)
223+
`,
224+
},
225+
{
226+
`existing_duplicate`,
227+
`module m
228+
require (
229+
x.y/a v1.0.0 // zero
230+
x.y/a v1.1.0 // one
231+
x.y/a v1.2.3 // two
232+
)
233+
`,
234+
[]require{
235+
{"x.y/a", "v1.2.3", true},
236+
},
237+
`module m
238+
require x.y/a v1.2.3 // indirect; zero
239+
`,
240+
},
241+
{
242+
`existing_duplicate_multi`,
243+
`module m
244+
require x.y/a v1.0.0 // zero
245+
require x.y/a v1.1.0 // one
246+
require x.y/a v1.2.3 // two
247+
`,
248+
[]require{
249+
{"x.y/a", "v1.2.3", true},
250+
},
251+
`module m
252+
require x.y/a v1.2.3 // indirect; zero
253+
`,
254+
},
196255
}
197256

198257
var addGoTests = []struct {
@@ -942,10 +1001,10 @@ func TestSetRequire(t *testing.T) {
9421001

9431002
f := testEdit(t, tt.in, tt.out, true, func(f *File) error {
9441003
f.SetRequire(mods)
1004+
f.Cleanup()
9451005
return nil
9461006
})
9471007

948-
f.Cleanup()
9491008
if len(f.Require) != len(mods) {
9501009
t.Errorf("after Cleanup, len(Require) = %v; want %v", len(f.Require), len(mods))
9511010
}

0 commit comments

Comments
 (0)