...

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

Documentation: cmd/vendor/golang.org/x/mod/modfile

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

View as plain text