work.go 6.5 KB

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