@@ -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.
10481053func (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