github.com/bir3/gocompiler@v0.9.2202/src/xvendor/golang.org/x/mod/modfile/work.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package modfile 6 7 import ( 8 "fmt" 9 "sort" 10 "strings" 11 ) 12 13 // A WorkFile is the parsed, interpreted form of a go.work file. 14 type WorkFile struct { 15 Go *Go 16 Toolchain *Toolchain 17 Use []*Use 18 Replace []*Replace 19 20 Syntax *FileSyntax 21 } 22 23 // A Use is a single directory statement. 24 type Use struct { 25 Path string // Use path of module. 26 ModulePath string // Module path in the comment. 27 Syntax *Line 28 } 29 30 // ParseWork parses and returns a go.work file. 31 // 32 // file is the name of the file, used in positions and errors. 33 // 34 // data is the content of the file. 35 // 36 // fix is an optional function that canonicalizes module versions. 37 // If fix is nil, all module versions must be canonical ([module.CanonicalVersion] 38 // must return the same string). 39 func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) { 40 fs, err := parse(file, data) 41 if err != nil { 42 return nil, err 43 } 44 f := &WorkFile{ 45 Syntax: fs, 46 } 47 var errs ErrorList 48 49 for _, x := range fs.Stmt { 50 switch x := x.(type) { 51 case *Line: 52 f.add(&errs, x, x.Token[0], x.Token[1:], fix) 53 54 case *LineBlock: 55 if len(x.Token) > 1 { 56 errs = append(errs, Error{ 57 Filename: file, 58 Pos: x.Start, 59 Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), 60 }) 61 continue 62 } 63 switch x.Token[0] { 64 default: 65 errs = append(errs, Error{ 66 Filename: file, 67 Pos: x.Start, 68 Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), 69 }) 70 continue 71 case "use", "replace": 72 for _, l := range x.Line { 73 f.add(&errs, l, x.Token[0], l.Token, fix) 74 } 75 } 76 } 77 } 78 79 if len(errs) > 0 { 80 return nil, errs 81 } 82 return f, nil 83 } 84 85 // Cleanup cleans up the file f after any edit operations. 86 // To avoid quadratic behavior, modifications like [WorkFile.DropRequire] 87 // clear the entry but do not remove it from the slice. 88 // Cleanup cleans out all the cleared entries. 89 func (f *WorkFile) Cleanup() { 90 w := 0 91 for _, r := range f.Use { 92 if r.Path != "" { 93 f.Use[w] = r 94 w++ 95 } 96 } 97 f.Use = f.Use[:w] 98 99 w = 0 100 for _, r := range f.Replace { 101 if r.Old.Path != "" { 102 f.Replace[w] = r 103 w++ 104 } 105 } 106 f.Replace = f.Replace[:w] 107 108 f.Syntax.Cleanup() 109 } 110 111 func (f *WorkFile) AddGoStmt(version string) error { 112 if !GoVersionRE.MatchString(version) { 113 return fmt.Errorf("invalid language version %q", version) 114 } 115 if f.Go == nil { 116 stmt := &Line{Token: []string{"go", version}} 117 f.Go = &Go{ 118 Version: version, 119 Syntax: stmt, 120 } 121 // Find the first non-comment-only block and add 122 // the go statement before it. That will keep file comments at the top. 123 i := 0 124 for i = 0; i < len(f.Syntax.Stmt); i++ { 125 if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { 126 break 127 } 128 } 129 f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) 130 } else { 131 f.Go.Version = version 132 f.Syntax.updateLine(f.Go.Syntax, "go", version) 133 } 134 return nil 135 } 136 137 func (f *WorkFile) AddToolchainStmt(name string) error { 138 if !ToolchainRE.MatchString(name) { 139 return fmt.Errorf("invalid toolchain name %q", name) 140 } 141 if f.Toolchain == nil { 142 stmt := &Line{Token: []string{"toolchain", name}} 143 f.Toolchain = &Toolchain{ 144 Name: name, 145 Syntax: stmt, 146 } 147 // Find the go line and add the toolchain line after it. 148 // Or else find the first non-comment-only block and add 149 // the toolchain line before it. That will keep file comments at the top. 150 i := 0 151 for i = 0; i < len(f.Syntax.Stmt); i++ { 152 if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" { 153 i++ 154 goto Found 155 } 156 } 157 for i = 0; i < len(f.Syntax.Stmt); i++ { 158 if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { 159 break 160 } 161 } 162 Found: 163 f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) 164 } else { 165 f.Toolchain.Name = name 166 f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) 167 } 168 return nil 169 } 170 171 // DropGoStmt deletes the go statement from the file. 172 func (f *WorkFile) DropGoStmt() { 173 if f.Go != nil { 174 f.Go.Syntax.markRemoved() 175 f.Go = nil 176 } 177 } 178 179 // DropToolchainStmt deletes the toolchain statement from the file. 180 func (f *WorkFile) DropToolchainStmt() { 181 if f.Toolchain != nil { 182 f.Toolchain.Syntax.markRemoved() 183 f.Toolchain = nil 184 } 185 } 186 187 func (f *WorkFile) AddUse(diskPath, modulePath string) error { 188 need := true 189 for _, d := range f.Use { 190 if d.Path == diskPath { 191 if need { 192 d.ModulePath = modulePath 193 f.Syntax.updateLine(d.Syntax, "use", AutoQuote(diskPath)) 194 need = false 195 } else { 196 d.Syntax.markRemoved() 197 *d = Use{} 198 } 199 } 200 } 201 202 if need { 203 f.AddNewUse(diskPath, modulePath) 204 } 205 return nil 206 } 207 208 func (f *WorkFile) AddNewUse(diskPath, modulePath string) { 209 line := f.Syntax.addLine(nil, "use", AutoQuote(diskPath)) 210 f.Use = append(f.Use, &Use{Path: diskPath, ModulePath: modulePath, Syntax: line}) 211 } 212 213 func (f *WorkFile) SetUse(dirs []*Use) { 214 need := make(map[string]string) 215 for _, d := range dirs { 216 need[d.Path] = d.ModulePath 217 } 218 219 for _, d := range f.Use { 220 if modulePath, ok := need[d.Path]; ok { 221 d.ModulePath = modulePath 222 } else { 223 d.Syntax.markRemoved() 224 *d = Use{} 225 } 226 } 227 228 // TODO(#45713): Add module path to comment. 229 230 for diskPath, modulePath := range need { 231 f.AddNewUse(diskPath, modulePath) 232 } 233 f.SortBlocks() 234 } 235 236 func (f *WorkFile) DropUse(path string) error { 237 for _, d := range f.Use { 238 if d.Path == path { 239 d.Syntax.markRemoved() 240 *d = Use{} 241 } 242 } 243 return nil 244 } 245 246 func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error { 247 return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) 248 } 249 250 func (f *WorkFile) DropReplace(oldPath, oldVers string) error { 251 for _, r := range f.Replace { 252 if r.Old.Path == oldPath && r.Old.Version == oldVers { 253 r.Syntax.markRemoved() 254 *r = Replace{} 255 } 256 } 257 return nil 258 } 259 260 func (f *WorkFile) SortBlocks() { 261 f.removeDups() // otherwise sorting is unsafe 262 263 for _, stmt := range f.Syntax.Stmt { 264 block, ok := stmt.(*LineBlock) 265 if !ok { 266 continue 267 } 268 sort.SliceStable(block.Line, func(i, j int) bool { 269 return lineLess(block.Line[i], block.Line[j]) 270 }) 271 } 272 } 273 274 // removeDups removes duplicate replace directives. 275 // 276 // Later replace directives take priority. 277 // 278 // require directives are not de-duplicated. That's left up to higher-level 279 // logic (MVS). 280 // 281 // retract directives are not de-duplicated since comments are 282 // meaningful, and versions may be retracted multiple times. 283 func (f *WorkFile) removeDups() { 284 removeDups(f.Syntax, nil, &f.Replace) 285 }