Source file src/cmd/vendor/golang.org/x/mod/modfile/work.go

     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  	Godebug   []*Godebug
    18  	Use       []*Use
    19  	Replace   []*Replace
    20  
    21  	Syntax *FileSyntax
    22  }
    23  
    24  // A Use is a single directory statement.
    25  type Use struct {
    26  	Path       string // Use path of module.
    27  	ModulePath string // Module path in the comment.
    28  	Syntax     *Line
    29  }
    30  
    31  // ParseWork parses and returns a go.work file.
    32  //
    33  // file is the name of the file, used in positions and errors.
    34  //
    35  // data is the content of the file.
    36  //
    37  // fix is an optional function that canonicalizes module versions.
    38  // If fix is nil, all module versions must be canonical ([module.CanonicalVersion]
    39  // must return the same string).
    40  func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) {
    41  	fs, err := parse(file, data)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	f := &WorkFile{
    46  		Syntax: fs,
    47  	}
    48  	var errs ErrorList
    49  
    50  	for _, x := range fs.Stmt {
    51  		switch x := x.(type) {
    52  		case *Line:
    53  			f.add(&errs, x, x.Token[0], x.Token[1:], fix)
    54  
    55  		case *LineBlock:
    56  			if len(x.Token) > 1 {
    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  			}
    64  			switch x.Token[0] {
    65  			default:
    66  				errs = append(errs, Error{
    67  					Filename: file,
    68  					Pos:      x.Start,
    69  					Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
    70  				})
    71  				continue
    72  			case "godebug", "use", "replace":
    73  				for _, l := range x.Line {
    74  					f.add(&errs, l, x.Token[0], l.Token, fix)
    75  				}
    76  			}
    77  		}
    78  	}
    79  
    80  	if len(errs) > 0 {
    81  		return nil, errs
    82  	}
    83  	return f, nil
    84  }
    85  
    86  // Cleanup cleans up the file f after any edit operations.
    87  // To avoid quadratic behavior, modifications like [WorkFile.DropRequire]
    88  // clear the entry but do not remove it from the slice.
    89  // Cleanup cleans out all the cleared entries.
    90  func (f *WorkFile) Cleanup() {
    91  	w := 0
    92  	for _, r := range f.Use {
    93  		if r.Path != "" {
    94  			f.Use[w] = r
    95  			w++
    96  		}
    97  	}
    98  	f.Use = f.Use[:w]
    99  
   100  	w = 0
   101  	for _, r := range f.Replace {
   102  		if r.Old.Path != "" {
   103  			f.Replace[w] = r
   104  			w++
   105  		}
   106  	}
   107  	f.Replace = f.Replace[:w]
   108  
   109  	f.Syntax.Cleanup()
   110  }
   111  
   112  func (f *WorkFile) AddGoStmt(version string) error {
   113  	if !GoVersionRE.MatchString(version) {
   114  		return fmt.Errorf("invalid language version %q", version)
   115  	}
   116  	if f.Go == nil {
   117  		stmt := &Line{Token: []string{"go", version}}
   118  		f.Go = &Go{
   119  			Version: version,
   120  			Syntax:  stmt,
   121  		}
   122  		// Find the first non-comment-only block and add
   123  		// the go statement before it. That will keep file comments at the top.
   124  		i := 0
   125  		for i = 0; i < len(f.Syntax.Stmt); i++ {
   126  			if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
   127  				break
   128  			}
   129  		}
   130  		f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
   131  	} else {
   132  		f.Go.Version = version
   133  		f.Syntax.updateLine(f.Go.Syntax, "go", version)
   134  	}
   135  	return nil
   136  }
   137  
   138  func (f *WorkFile) AddToolchainStmt(name string) error {
   139  	if !ToolchainRE.MatchString(name) {
   140  		return fmt.Errorf("invalid toolchain name %q", name)
   141  	}
   142  	if f.Toolchain == nil {
   143  		stmt := &Line{Token: []string{"toolchain", name}}
   144  		f.Toolchain = &Toolchain{
   145  			Name:   name,
   146  			Syntax: stmt,
   147  		}
   148  		// Find the go line and add the toolchain line after it.
   149  		// Or else find the first non-comment-only block and add
   150  		// the toolchain line before it. That will keep file comments at the top.
   151  		i := 0
   152  		for i = 0; i < len(f.Syntax.Stmt); i++ {
   153  			if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" {
   154  				i++
   155  				goto Found
   156  			}
   157  		}
   158  		for i = 0; i < len(f.Syntax.Stmt); i++ {
   159  			if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
   160  				break
   161  			}
   162  		}
   163  	Found:
   164  		f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
   165  	} else {
   166  		f.Toolchain.Name = name
   167  		f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
   168  	}
   169  	return nil
   170  }
   171  
   172  // DropGoStmt deletes the go statement from the file.
   173  func (f *WorkFile) DropGoStmt() {
   174  	if f.Go != nil {
   175  		f.Go.Syntax.markRemoved()
   176  		f.Go = nil
   177  	}
   178  }
   179  
   180  // DropToolchainStmt deletes the toolchain statement from the file.
   181  func (f *WorkFile) DropToolchainStmt() {
   182  	if f.Toolchain != nil {
   183  		f.Toolchain.Syntax.markRemoved()
   184  		f.Toolchain = nil
   185  	}
   186  }
   187  
   188  // AddGodebug sets the first godebug line for key to value,
   189  // preserving any existing comments for that line and removing all
   190  // other godebug lines for key.
   191  //
   192  // If no line currently exists for key, AddGodebug adds a new line
   193  // at the end of the last godebug block.
   194  func (f *WorkFile) AddGodebug(key, value string) error {
   195  	need := true
   196  	for _, g := range f.Godebug {
   197  		if g.Key == key {
   198  			if need {
   199  				g.Value = value
   200  				f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value)
   201  				need = false
   202  			} else {
   203  				g.Syntax.markRemoved()
   204  				*g = Godebug{}
   205  			}
   206  		}
   207  	}
   208  
   209  	if need {
   210  		f.addNewGodebug(key, value)
   211  	}
   212  	return nil
   213  }
   214  
   215  // addNewGodebug adds a new godebug key=value line at the end
   216  // of the last godebug block, regardless of any existing godebug lines for key.
   217  func (f *WorkFile) addNewGodebug(key, value string) {
   218  	line := f.Syntax.addLine(nil, "godebug", key+"="+value)
   219  	g := &Godebug{
   220  		Key:    key,
   221  		Value:  value,
   222  		Syntax: line,
   223  	}
   224  	f.Godebug = append(f.Godebug, g)
   225  }
   226  
   227  func (f *WorkFile) DropGodebug(key string) error {
   228  	for _, g := range f.Godebug {
   229  		if g.Key == key {
   230  			g.Syntax.markRemoved()
   231  			*g = Godebug{}
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  func (f *WorkFile) AddUse(diskPath, modulePath string) error {
   238  	need := true
   239  	for _, d := range f.Use {
   240  		if d.Path == diskPath {
   241  			if need {
   242  				d.ModulePath = modulePath
   243  				f.Syntax.updateLine(d.Syntax, "use", AutoQuote(diskPath))
   244  				need = false
   245  			} else {
   246  				d.Syntax.markRemoved()
   247  				*d = Use{}
   248  			}
   249  		}
   250  	}
   251  
   252  	if need {
   253  		f.AddNewUse(diskPath, modulePath)
   254  	}
   255  	return nil
   256  }
   257  
   258  func (f *WorkFile) AddNewUse(diskPath, modulePath string) {
   259  	line := f.Syntax.addLine(nil, "use", AutoQuote(diskPath))
   260  	f.Use = append(f.Use, &Use{Path: diskPath, ModulePath: modulePath, Syntax: line})
   261  }
   262  
   263  func (f *WorkFile) SetUse(dirs []*Use) {
   264  	need := make(map[string]string)
   265  	for _, d := range dirs {
   266  		need[d.Path] = d.ModulePath
   267  	}
   268  
   269  	for _, d := range f.Use {
   270  		if modulePath, ok := need[d.Path]; ok {
   271  			d.ModulePath = modulePath
   272  		} else {
   273  			d.Syntax.markRemoved()
   274  			*d = Use{}
   275  		}
   276  	}
   277  
   278  	// TODO(#45713): Add module path to comment.
   279  
   280  	for diskPath, modulePath := range need {
   281  		f.AddNewUse(diskPath, modulePath)
   282  	}
   283  	f.SortBlocks()
   284  }
   285  
   286  func (f *WorkFile) DropUse(path string) error {
   287  	for _, d := range f.Use {
   288  		if d.Path == path {
   289  			d.Syntax.markRemoved()
   290  			*d = Use{}
   291  		}
   292  	}
   293  	return nil
   294  }
   295  
   296  func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error {
   297  	return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
   298  }
   299  
   300  func (f *WorkFile) DropReplace(oldPath, oldVers string) error {
   301  	for _, r := range f.Replace {
   302  		if r.Old.Path == oldPath && r.Old.Version == oldVers {
   303  			r.Syntax.markRemoved()
   304  			*r = Replace{}
   305  		}
   306  	}
   307  	return nil
   308  }
   309  
   310  func (f *WorkFile) SortBlocks() {
   311  	f.removeDups() // otherwise sorting is unsafe
   312  
   313  	for _, stmt := range f.Syntax.Stmt {
   314  		block, ok := stmt.(*LineBlock)
   315  		if !ok {
   316  			continue
   317  		}
   318  		sort.SliceStable(block.Line, func(i, j int) bool {
   319  			return lineLess(block.Line[i], block.Line[j])
   320  		})
   321  	}
   322  }
   323  
   324  // removeDups removes duplicate replace directives.
   325  //
   326  // Later replace directives take priority.
   327  //
   328  // require directives are not de-duplicated. That's left up to higher-level
   329  // logic (MVS).
   330  //
   331  // retract directives are not de-duplicated since comments are
   332  // meaningful, and versions may be retracted multiple times.
   333  func (f *WorkFile) removeDups() {
   334  	removeDups(f.Syntax, nil, &f.Replace, nil)
   335  }
   336  

View as plain text