@@ -954,6 +954,175 @@ func (f *File) SetRequire(req []*Require) {
954954 f .SortBlocks ()
955955}
956956
957+ // SetRequireSeparateIndirect updates the requirements of f to contain the given
958+ // requirements. Comment contents (except for 'indirect' markings) are retained
959+ // from the first existing requirement for each module path, and block structure
960+ // is maintained as long as the indirect markings match.
961+ //
962+ // Any requirements on paths not already present in the file are added. Direct
963+ // requirements are added to the last block containing *any* other direct
964+ // requirement. Indirect requirements are added to the last block containing
965+ // *only* other indirect requirements. If no suitable block exists, a new one is
966+ // added, with the last block containing a direct dependency (if any)
967+ // immediately before the first block containing only indirect dependencies.
968+ //
969+ // The Syntax field is ignored for requirements in the given blocks.
970+ func (f * File ) SetRequireSeparateIndirect (req []* Require ) {
971+ type modKey struct {
972+ path string
973+ indirect bool
974+ }
975+ need := make (map [modKey ]string )
976+ for _ , r := range req {
977+ need [modKey {r .Mod .Path , r .Indirect }] = r .Mod .Version
978+ }
979+
980+ comments := make (map [string ]Comments )
981+ for _ , r := range f .Require {
982+ v , ok := need [modKey {r .Mod .Path , r .Indirect }]
983+ if ! ok {
984+ if _ , ok := need [modKey {r .Mod .Path , ! r .Indirect }]; ok {
985+ if _ , dup := comments [r .Mod .Path ]; ! dup {
986+ comments [r .Mod .Path ] = r .Syntax .Comments
987+ }
988+ }
989+ r .markRemoved ()
990+ continue
991+ }
992+ r .setVersion (v )
993+ delete (need , modKey {r .Mod .Path , r .Indirect })
994+ }
995+
996+ var (
997+ lastDirectOrMixedBlock Expr
998+ firstIndirectOnlyBlock Expr
999+ lastIndirectOnlyBlock Expr
1000+ )
1001+ for _ , stmt := range f .Syntax .Stmt {
1002+ switch stmt := stmt .(type ) {
1003+ case * Line :
1004+ if len (stmt .Token ) == 0 || stmt .Token [0 ] != "require" {
1005+ continue
1006+ }
1007+ if isIndirect (stmt ) {
1008+ lastIndirectOnlyBlock = stmt
1009+ } else {
1010+ lastDirectOrMixedBlock = stmt
1011+ }
1012+ case * LineBlock :
1013+ if len (stmt .Token ) == 0 || stmt .Token [0 ] != "require" {
1014+ continue
1015+ }
1016+ indirectOnly := true
1017+ for _ , line := range stmt .Line {
1018+ if len (line .Token ) == 0 {
1019+ continue
1020+ }
1021+ if ! isIndirect (line ) {
1022+ indirectOnly = false
1023+ break
1024+ }
1025+ }
1026+ if indirectOnly {
1027+ lastIndirectOnlyBlock = stmt
1028+ if firstIndirectOnlyBlock == nil {
1029+ firstIndirectOnlyBlock = stmt
1030+ }
1031+ } else {
1032+ lastDirectOrMixedBlock = stmt
1033+ }
1034+ }
1035+ }
1036+
1037+ isOrContainsStmt := func (stmt Expr , target Expr ) bool {
1038+ if stmt == target {
1039+ return true
1040+ }
1041+ if stmt , ok := stmt .(* LineBlock ); ok {
1042+ if target , ok := target .(* Line ); ok {
1043+ for _ , line := range stmt .Line {
1044+ if line == target {
1045+ return true
1046+ }
1047+ }
1048+ }
1049+ }
1050+ return false
1051+ }
1052+
1053+ addRequire := func (path , vers string , indirect bool , comments Comments ) {
1054+ var line * Line
1055+ if indirect {
1056+ if lastIndirectOnlyBlock != nil {
1057+ line = f .Syntax .addLine (lastIndirectOnlyBlock , "require" , path , vers )
1058+ } else {
1059+ // Add a new require block after the last direct-only or mixed "require"
1060+ // block (if any).
1061+ //
1062+ // (f.Syntax.addLine would add the line to an existing "require" block if
1063+ // present, but here the existing "require" blocks are all direct-only, so
1064+ // we know we need to add a new block instead.)
1065+ line = & Line {Token : []string {"require" , path , vers }}
1066+ lastIndirectOnlyBlock = line
1067+ firstIndirectOnlyBlock = line // only block implies first block
1068+ if lastDirectOrMixedBlock == nil {
1069+ f .Syntax .Stmt = append (f .Syntax .Stmt , line )
1070+ } else {
1071+ for i , stmt := range f .Syntax .Stmt {
1072+ if isOrContainsStmt (stmt , lastDirectOrMixedBlock ) {
1073+ f .Syntax .Stmt = append (f .Syntax .Stmt , nil ) // increase size
1074+ copy (f .Syntax .Stmt [i + 2 :], f .Syntax .Stmt [i + 1 :]) // shuffle elements up
1075+ f .Syntax .Stmt [i + 1 ] = line
1076+ break
1077+ }
1078+ }
1079+ }
1080+ }
1081+ } else {
1082+ if lastDirectOrMixedBlock != nil {
1083+ line = f .Syntax .addLine (lastDirectOrMixedBlock , "require" , path , vers )
1084+ } else {
1085+ // Add a new require block before the first indirect block (if any).
1086+ //
1087+ // That way if the file initially contains only indirect lines,
1088+ // the direct lines still appear before it: we preserve existing
1089+ // structure, but only to the extent that that structure already
1090+ // reflects the direct/indirect split.
1091+ line = & Line {Token : []string {"require" , path , vers }}
1092+ lastDirectOrMixedBlock = line
1093+ if firstIndirectOnlyBlock == nil {
1094+ f .Syntax .Stmt = append (f .Syntax .Stmt , line )
1095+ } else {
1096+ for i , stmt := range f .Syntax .Stmt {
1097+ if isOrContainsStmt (stmt , firstIndirectOnlyBlock ) {
1098+ f .Syntax .Stmt = append (f .Syntax .Stmt , nil ) // increase size
1099+ copy (f .Syntax .Stmt [i + 1 :], f .Syntax .Stmt [i :]) // shuffle elements up
1100+ f .Syntax .Stmt [i ] = line
1101+ break
1102+ }
1103+ }
1104+ }
1105+ }
1106+ }
1107+
1108+ line .Comments .Before = commentsAdd (line .Comments .Before , comments .Before )
1109+ line .Comments .Suffix = commentsAdd (line .Comments .Suffix , comments .Suffix )
1110+
1111+ r := & Require {
1112+ Mod : module.Version {Path : path , Version : vers },
1113+ Indirect : indirect ,
1114+ Syntax : line ,
1115+ }
1116+ r .setIndirect (indirect )
1117+ f .Require = append (f .Require , r )
1118+ }
1119+
1120+ for k , vers := range need {
1121+ addRequire (k .path , vers , k .indirect , comments [k .path ])
1122+ }
1123+ f .SortBlocks ()
1124+ }
1125+
9571126func (f * File ) DropRequire (path string ) error {
9581127 for _ , r := range f .Require {
9591128 if r .Mod .Path == path {
0 commit comments