Skip to content

Commit 4be982b

Browse files
author
Jay Conrod
committed
modfile: in SetRequireSeparateIndirect, arrange requirements consistently
SetRequireSeparateIndirect now makes a stronger attempt to keep automatically added requirements in two blocks: one containing only direct requirements and one containing only indirect requirements. SetRequireSeparateIndirect will find or add these two blocks (commented blocks are left alone). New requirements are added to one of these two blocks. Existing requirements may be moved between these two blocks if their markings change. SetRequireSeparateIndirect attempts to preserve existing structure in the file by not adding requirements to or moving requirements from blocks with block-level comments and blocks other than the last uncommented direct-only and indirect-only block. As an exception to aid with migration, if the file contains a single uncommented block of requirements (as would the be the case if the user started with a 1.16 go.mod file, changed the go directive to 1.17, then ran 'go mod tidy'), SetRequireSeparateIndirect will split the block into direct-only and indirect-only blocks. This is a change in behavior, but it has no semantic effect, and it should result in cleaner, more stable go.mod files. For golang/go#47563 For golang/go#47733 Change-Id: Ifa20bb084c6bdaf1e00140600380857de8afa320 Reviewed-on: https://go-review.googlesource.com/c/mod/+/343431 Trust: Jay Conrod <[email protected]> Trust: Bryan C. Mills <[email protected]> Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 1b1db11 commit 4be982b

File tree

2 files changed

+249
-155
lines changed

2 files changed

+249
-155
lines changed

modfile/rule.go

Lines changed: 148 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,170 +1034,190 @@ func (f *File) SetRequire(req []*Require) {
10341034

10351035
// SetRequireSeparateIndirect updates the requirements of f to contain the given
10361036
// requirements. Comment contents (except for 'indirect' markings) are retained
1037-
// from the first existing requirement for each module path, and block structure
1038-
// is maintained as long as the indirect markings match.
1037+
// from the first existing requirement for each module path. Like SetRequire,
1038+
// SetRequireSeparateIndirect adds requirements for new paths in req,
1039+
// updates the version and "// indirect" comment on existing requirements,
1040+
// and deletes requirements on paths not in req. Existing duplicate requirements
1041+
// are deleted.
10391042
//
1040-
// Any requirements on paths not already present in the file are added. Direct
1041-
// requirements are added to the last block containing *any* other direct
1042-
// requirement. Indirect requirements are added to the last block containing
1043-
// *only* other indirect requirements. If no suitable block exists, a new one is
1044-
// added, with the last block containing a direct dependency (if any)
1045-
// immediately before the first block containing only indirect dependencies.
1043+
// As its name suggests, SetRequireSeparateIndirect puts direct and indirect
1044+
// requirements into two separate blocks, one containing only direct
1045+
// requirements, and the other containing only indirect requirements.
1046+
// SetRequireSeparateIndirect may move requirements between these two blocks
1047+
// when their indirect markings change. However, SetRequireSeparateIndirect
1048+
// won't move requirements from other blocks, especially blocks with comments.
10461049
//
1047-
// The Syntax field is ignored for requirements in the given blocks.
1050+
// If the file initially has one uncommented block of requirements,
1051+
// SetRequireSeparateIndirect will split it into a direct-only and indirect-only
1052+
// block. This aids in the transition to separate blocks.
10481053
func (f *File) SetRequireSeparateIndirect(req []*Require) {
1049-
type modKey struct {
1050-
path string
1051-
indirect bool
1052-
}
1053-
need := make(map[modKey]string)
1054-
for _, r := range req {
1055-
need[modKey{r.Mod.Path, r.Indirect}] = r.Mod.Version
1054+
// hasComments returns whether a line or block has comments
1055+
// other than "indirect".
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")
10561060
}
10571061

1058-
comments := make(map[string]Comments)
1059-
for _, r := range f.Require {
1060-
v, ok := need[modKey{r.Mod.Path, r.Indirect}]
1061-
if !ok {
1062-
if _, ok := need[modKey{r.Mod.Path, !r.Indirect}]; ok {
1063-
if _, dup := comments[r.Mod.Path]; !dup {
1064-
comments[r.Mod.Path] = r.Syntax.Comments
1065-
}
1062+
// moveReq adds r to block. If r was in another block, moveReq deletes
1063+
// it from that block and transfers its comments.
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)
10661071
}
1067-
r.markRemoved()
1068-
continue
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 // Cleanup will delete the old line.
1079+
r.Syntax = line
10691080
}
1070-
r.setVersion(v)
1071-
delete(need, modKey{r.Mod.Path, r.Indirect})
1081+
line.InBlock = true
1082+
block.Line = append(block.Line, line)
10721083
}
10731084

1085+
// Examine existing require lines and blocks.
10741086
var (
1075-
lastDirectOrMixedBlock Expr
1076-
firstIndirectOnlyBlock Expr
1077-
lastIndirectOnlyBlock Expr
1087+
// We may insert new requirements into the last uncommented
1088+
// direct-only and indirect-only blocks. We may also move requirements
1089+
// to the opposite block if their indirect markings change.
1090+
lastDirectBlock, lastIndirectBlock *LineBlock
1091+
lastDirectBlockIndex = -1
1092+
lastIndirectBlockIndex = -1
1093+
1094+
// If there are no direct-only or indirect-only blocks, a new block may
1095+
// be inserted after the last require line or block.
1096+
lastRequireIndex = -1
1097+
1098+
// If there's only one require line or block, and it's uncommented,
1099+
// we'll move its requirements to the direct-only or indirect-only blocks.
1100+
requireLineOrBlockCount = 0
1101+
1102+
// Track the block each requirement belongs to (if any) so we can
1103+
// move them later.
1104+
lineToBlock = make(map[*Line]*LineBlock)
10781105
)
1079-
for _, stmt := range f.Syntax.Stmt {
1106+
for i, stmt := range f.Syntax.Stmt {
10801107
switch stmt := stmt.(type) {
10811108
case *Line:
10821109
if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
10831110
continue
10841111
}
1085-
if isIndirect(stmt) {
1086-
lastIndirectOnlyBlock = stmt
1087-
} else {
1088-
lastDirectOrMixedBlock = stmt
1089-
}
1112+
lastRequireIndex = i
1113+
requireLineOrBlockCount++
1114+
10901115
case *LineBlock:
10911116
if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
10921117
continue
10931118
}
1094-
indirectOnly := true
1119+
lastRequireIndex = i
1120+
requireLineOrBlockCount++
1121+
allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
1122+
allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
10951123
for _, line := range stmt.Line {
1096-
if len(line.Token) == 0 {
1097-
continue
1098-
}
1099-
if !isIndirect(line) {
1100-
indirectOnly = false
1101-
break
1124+
lineToBlock[line] = stmt
1125+
if hasComments(line.Comments) {
1126+
allDirect = false
1127+
allIndirect = false
1128+
} else if isIndirect(line) {
1129+
allDirect = false
1130+
} else {
1131+
allIndirect = false
11021132
}
11031133
}
1104-
if indirectOnly {
1105-
lastIndirectOnlyBlock = stmt
1106-
if firstIndirectOnlyBlock == nil {
1107-
firstIndirectOnlyBlock = stmt
1108-
}
1109-
} else {
1110-
lastDirectOrMixedBlock = stmt
1134+
if allDirect {
1135+
lastDirectBlock = stmt
1136+
lastDirectBlockIndex = i
1137+
}
1138+
if allIndirect {
1139+
lastIndirectBlock = stmt
1140+
lastIndirectBlockIndex = i
11111141
}
11121142
}
11131143
}
11141144

1115-
isOrContainsStmt := func(stmt Expr, target Expr) bool {
1116-
if stmt == target {
1117-
return true
1118-
}
1119-
if stmt, ok := stmt.(*LineBlock); ok {
1120-
if target, ok := target.(*Line); ok {
1121-
for _, line := range stmt.Line {
1122-
if line == target {
1123-
return true
1124-
}
1125-
}
1126-
}
1145+
oneFlatUncommentedBlock := requireLineOrBlockCount == 1 &&
1146+
!hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment())
1147+
1148+
// Create direct and indirect blocks if needed. It's okay if they're empty.
1149+
// Cleanup will remove them and convert one-line blocks to lines.
1150+
if lastDirectBlock == nil {
1151+
lastDirectBlock = &LineBlock{Token: []string{"require"}}
1152+
i := len(f.Syntax.Stmt)
1153+
if lastIndirectBlockIndex >= 0 {
1154+
i = lastIndirectBlockIndex
1155+
lastIndirectBlockIndex++
1156+
} else if lastRequireIndex >= 0 {
1157+
i = lastRequireIndex + 1
11271158
}
1128-
return false
1159+
f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
1160+
copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
1161+
f.Syntax.Stmt[i] = lastDirectBlock
1162+
lastDirectBlockIndex = i
11291163
}
11301164

1131-
addRequire := func(path, vers string, indirect bool, comments Comments) {
1132-
var line *Line
1133-
if indirect {
1134-
if lastIndirectOnlyBlock != nil {
1135-
line = f.Syntax.addLine(lastIndirectOnlyBlock, "require", path, vers)
1136-
} else {
1137-
// Add a new require block after the last direct-only or mixed "require"
1138-
// block (if any).
1139-
//
1140-
// (f.Syntax.addLine would add the line to an existing "require" block if
1141-
// present, but here the existing "require" blocks are all direct-only, so
1142-
// we know we need to add a new block instead.)
1143-
line = &Line{Token: []string{"require", path, vers}}
1144-
lastIndirectOnlyBlock = line
1145-
firstIndirectOnlyBlock = line // only block implies first block
1146-
if lastDirectOrMixedBlock == nil {
1147-
f.Syntax.Stmt = append(f.Syntax.Stmt, line)
1148-
} else {
1149-
for i, stmt := range f.Syntax.Stmt {
1150-
if isOrContainsStmt(stmt, lastDirectOrMixedBlock) {
1151-
f.Syntax.Stmt = append(f.Syntax.Stmt, nil) // increase size
1152-
copy(f.Syntax.Stmt[i+2:], f.Syntax.Stmt[i+1:]) // shuffle elements up
1153-
f.Syntax.Stmt[i+1] = line
1154-
break
1155-
}
1156-
}
1157-
}
1158-
}
1159-
} else {
1160-
if lastDirectOrMixedBlock != nil {
1161-
line = f.Syntax.addLine(lastDirectOrMixedBlock, "require", path, vers)
1162-
} else {
1163-
// Add a new require block before the first indirect block (if any).
1164-
//
1165-
// That way if the file initially contains only indirect lines,
1166-
// the direct lines still appear before it: we preserve existing
1167-
// structure, but only to the extent that that structure already
1168-
// reflects the direct/indirect split.
1169-
line = &Line{Token: []string{"require", path, vers}}
1170-
lastDirectOrMixedBlock = line
1171-
if firstIndirectOnlyBlock == nil {
1172-
f.Syntax.Stmt = append(f.Syntax.Stmt, line)
1173-
} else {
1174-
for i, stmt := range f.Syntax.Stmt {
1175-
if isOrContainsStmt(stmt, firstIndirectOnlyBlock) {
1176-
f.Syntax.Stmt = append(f.Syntax.Stmt, nil) // increase size
1177-
copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:]) // shuffle elements up
1178-
f.Syntax.Stmt[i] = line
1179-
break
1180-
}
1181-
}
1182-
}
1183-
}
1165+
if lastIndirectBlock == nil {
1166+
lastIndirectBlock = &LineBlock{Token: []string{"require"}}
1167+
i := len(f.Syntax.Stmt)
1168+
if lastDirectBlockIndex >= 0 {
1169+
i = lastDirectBlockIndex + 1
11841170
}
1171+
f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
1172+
copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
1173+
f.Syntax.Stmt[i] = lastIndirectBlock
1174+
lastIndirectBlockIndex = i
1175+
}
11851176

1186-
line.Comments.Before = commentsAdd(line.Comments.Before, comments.Before)
1187-
line.Comments.Suffix = commentsAdd(line.Comments.Suffix, comments.Suffix)
1188-
1189-
r := &Require{
1190-
Mod: module.Version{Path: path, Version: vers},
1191-
Indirect: indirect,
1192-
Syntax: line,
1177+
// Delete requirements we don't want anymore.
1178+
// Update versions and indirect comments on requirements we want to keep.
1179+
// If a requirement is in last{Direct,Indirect}Block with the wrong
1180+
// indirect marking after this, or if the requirement is in an single
1181+
// uncommented mixed block (oneFlatUncommentedBlock), move it to the
1182+
// correct block.
1183+
//
1184+
// Some blocks may be empty after this. Cleanup will remove them.
1185+
need := make(map[string]*Require)
1186+
for _, r := range req {
1187+
need[r.Mod.Path] = r
1188+
}
1189+
have := make(map[string]*Require)
1190+
for _, r := range f.Require {
1191+
path := r.Mod.Path
1192+
if need[path] == nil || have[path] != nil {
1193+
// Requirement not needed, or duplicate requirement. Delete.
1194+
r.markRemoved()
1195+
continue
1196+
}
1197+
have[r.Mod.Path] = r
1198+
r.setVersion(need[path].Mod.Version)
1199+
r.setIndirect(need[path].Indirect)
1200+
if need[path].Indirect &&
1201+
(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
1202+
moveReq(r, lastIndirectBlock)
1203+
} else if !need[path].Indirect &&
1204+
(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
1205+
moveReq(r, lastDirectBlock)
11931206
}
1194-
r.setIndirect(indirect)
1195-
f.Require = append(f.Require, r)
11961207
}
11971208

1198-
for k, vers := range need {
1199-
addRequire(k.path, vers, k.indirect, comments[k.path])
1209+
// Add new requirements.
1210+
for path, r := range need {
1211+
if have[path] == nil {
1212+
if r.Indirect {
1213+
moveReq(r, lastIndirectBlock)
1214+
} else {
1215+
moveReq(r, lastDirectBlock)
1216+
}
1217+
f.Require = append(f.Require, r)
1218+
}
12001219
}
1220+
12011221
f.SortBlocks()
12021222
}
12031223

0 commit comments

Comments
 (0)