Skip to content

Commit ed6e249

Browse files
committed
fix: AddNewRequire block ordering
Try to maintain the expected block ordering with direct requires in the first block and indirect requires in the second block. Also clarify expected use of SetRequireSeparateIndirect to avoid panic. Fixes #69050
1 parent 46a3137 commit ed6e249

File tree

3 files changed

+377
-11
lines changed

3 files changed

+377
-11
lines changed

modfile/read.go

+105
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"unicode/utf8"
1616
)
1717

18+
// requireToken is the token used for require statements.
19+
const requireToken = "require"
20+
1821
// A Position describes an arbitrary source position in a file, including the
1922
// file, line, column, and byte offset.
2023
type Position struct {
@@ -187,6 +190,108 @@ func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
187190
return new
188191
}
189192

193+
// requireHint returns a hint for adding a new require line.
194+
// It tries to maintain the standard order of require blocks, with
195+
// the first block being the direct requires and the second block
196+
// being the indirect requires.
197+
func (x *FileSyntax) requireHint(indirect bool) Expr {
198+
if indirect {
199+
// Indirect block requested return the first require
200+
// block with indirect dependencies.
201+
for _, stmt := range x.Stmt {
202+
switch stmt := stmt.(type) {
203+
case *Line:
204+
if stmt.Token != nil && stmt.Token[0] == requireToken && isIndirect(stmt) {
205+
return stmt
206+
}
207+
case *LineBlock:
208+
if stmt.Token[0] == requireToken {
209+
for _, line := range stmt.Line {
210+
if isIndirect(line) {
211+
return line
212+
}
213+
}
214+
}
215+
}
216+
}
217+
218+
// No indirect require found, append a new block and return
219+
// is as the hint. This prevents adding the indirect to an
220+
// existing direct block.
221+
block := &LineBlock{
222+
Token: []string{requireToken},
223+
}
224+
x.Stmt = append(x.Stmt, block)
225+
226+
return block
227+
}
228+
229+
// Direct block requested.
230+
var last Expr
231+
for _, stmt := range x.Stmt {
232+
switch stmt := stmt.(type) {
233+
case *Line:
234+
if stmt.Token != nil && stmt.Token[0] == requireToken {
235+
if !isIndirect(stmt) {
236+
// Direct require line found, return it as hint to
237+
// combine with it.
238+
return stmt
239+
}
240+
241+
// Indirect line first, which is unexpected. We return
242+
// the last stmt as hint to create a new require block
243+
// before it, to try to maintain the standard order.
244+
if last != nil {
245+
return last
246+
}
247+
248+
// Indirect line at the beginning of the file, prepend
249+
// a new require block and return it as hint, to try to
250+
// maintain the standard order.
251+
block := &LineBlock{
252+
Token: []string{requireToken},
253+
}
254+
x.Stmt = append([]Expr{block}, x.Stmt...)
255+
256+
return block
257+
}
258+
case *LineBlock:
259+
if stmt.Token[0] == requireToken {
260+
for _, line := range stmt.Line {
261+
if !isIndirect(line) {
262+
// Direct require block found, return it as hint to
263+
// combine with it.
264+
return line
265+
}
266+
}
267+
268+
// Indirect block before first, which is unexpected.
269+
// Return the last stmt as hint to create a new require
270+
// block before it to try to maintain the standard order.
271+
if last != nil {
272+
return last
273+
}
274+
275+
// Indirect block at the beginning of the file, which is
276+
// unexpected. Prepend a new require block and return it
277+
// as hint to try to maintain the standard order.
278+
block := &LineBlock{
279+
Token: []string{requireToken},
280+
}
281+
x.Stmt = append([]Expr{block}, x.Stmt...)
282+
283+
return block
284+
}
285+
}
286+
287+
last = stmt
288+
continue
289+
}
290+
291+
// No requires found, addLine will create one.
292+
return nil
293+
}
294+
190295
func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
191296
if line.InBlock {
192297
tokens = tokens[1:]

modfile/rule.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ func (f *File) addNewGodebug(key, value string) {
11441144
// other lines for path.
11451145
//
11461146
// If no line currently exists for path, AddRequire adds a new line
1147-
// at the end of the last require block.
1147+
// using AddNewRequire.
11481148
func (f *File) AddRequire(path, vers string) error {
11491149
need := true
11501150
for _, r := range f.Require {
@@ -1166,10 +1166,14 @@ func (f *File) AddRequire(path, vers string) error {
11661166
return nil
11671167
}
11681168

1169-
// AddNewRequire adds a new require line for path at version vers at the end of
1170-
// the last require block, regardless of any existing require lines for path.
1169+
// AddNewRequire adds a new require line for path at version vers, regardless
1170+
// of any existing require lines for path.
1171+
// Similar to SetRequireSeparateIndirect it attempts to maintain the standard
1172+
// of having direct requires in the first block and indirect requires in the
1173+
// second block.
11711174
func (f *File) AddNewRequire(path, vers string, indirect bool) {
1172-
line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
1175+
hint := f.Syntax.requireHint(indirect)
1176+
line := f.Syntax.addLine(hint, "require", AutoQuote(path), vers)
11731177
r := &Require{
11741178
Mod: module.Version{Path: path, Version: vers},
11751179
Syntax: line,
@@ -1248,6 +1252,12 @@ func (f *File) SetRequire(req []*Require) {
12481252
// If the file initially has one uncommented block of requirements,
12491253
// SetRequireSeparateIndirect will split it into a direct-only and indirect-only
12501254
// block. This aids in the transition to separate blocks.
1255+
//
1256+
// New entries in req must not be added to the file.Require slice before calling
1257+
// otherwise this function will panic. So the calling convention should be along
1258+
// the lines of:
1259+
//
1260+
// File.SetRequireSeparateIndirect(append(File.Require, newReqs...))
12511261
func (f *File) SetRequireSeparateIndirect(req []*Require) {
12521262
// hasComments returns whether a line or block has comments
12531263
// other than "indirect".

0 commit comments

Comments
 (0)