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