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  	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  }
   235  

View as plain text