1// Copyright 2018 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 implements a parser and formatter for go.mod files.
6//
7// The go.mod syntax is described in
8// https://golang.org/cmd/go/#hdr-The_go_mod_file.
9//
10// The Parse and ParseLax functions both parse a go.mod file and return an
11// abstract syntax tree. ParseLax ignores unknown statements and may be used to
12// parse go.mod files that may have been developed with newer versions of Go.
13//
14// The File struct returned by Parse and ParseLax represent an abstract
15// go.mod file. File has several methods like AddNewRequire and DropReplace
16// that can be used to programmatically edit a file.
17//
18// The Format function formats a File back to a byte slice which can be
19// written to a file.
20package modfile
21
22import (
23	"errors"
24	"fmt"
25	"path/filepath"
26	"sort"
27	"strconv"
28	"strings"
29	"unicode"
30
31	"golang.org/x/mod/internal/lazyregexp"
32	"golang.org/x/mod/module"
33	"golang.org/x/mod/semver"
34)
35
36// A File is the parsed, interpreted form of a go.mod file.
37type File struct {
38	Module  *Module
39	Go      *Go
40	Require []*Require
41	Exclude []*Exclude
42	Replace []*Replace
43	Retract []*Retract
44
45	Syntax *FileSyntax
46}
47
48// A Module is the module statement.
49type Module struct {
50	Mod        module.Version
51	Deprecated string
52	Syntax     *Line
53}
54
55// A Go is the go statement.
56type Go struct {
57	Version string // "1.23"
58	Syntax  *Line
59}
60
61// An Exclude is a single exclude statement.
62type Exclude struct {
63	Mod    module.Version
64	Syntax *Line
65}
66
67// A Replace is a single replace statement.
68type Replace struct {
69	Old    module.Version
70	New    module.Version
71	Syntax *Line
72}
73
74// A Retract is a single retract statement.
75type Retract struct {
76	VersionInterval
77	Rationale string
78	Syntax    *Line
79}
80
81// A VersionInterval represents a range of versions with upper and lower bounds.
82// Intervals are closed: both bounds are included. When Low is equal to High,
83// the interval may refer to a single version ('v1.2.3') or an interval
84// ('[v1.2.3, v1.2.3]'); both have the same representation.
85type VersionInterval struct {
86	Low, High string
87}
88
89// A Require is a single require statement.
90type Require struct {
91	Mod      module.Version
92	Indirect bool // has "// indirect" comment
93	Syntax   *Line
94}
95
96func (r *Require) markRemoved() {
97	r.Syntax.markRemoved()
98	*r = Require{}
99}
100
101func (r *Require) setVersion(v string) {
102	r.Mod.Version = v
103
104	if line := r.Syntax; len(line.Token) > 0 {
105		if line.InBlock {
106			// If the line is preceded by an empty line, remove it; see
107			// https://golang.org/issue/33779.
108			if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
109				line.Comments.Before = line.Comments.Before[:0]
110			}
111			if len(line.Token) >= 2 { // example.com v1.2.3
112				line.Token[1] = v
113			}
114		} else {
115			if len(line.Token) >= 3 { // require example.com v1.2.3
116				line.Token[2] = v
117			}
118		}
119	}
120}
121
122// setIndirect sets line to have (or not have) a "// indirect" comment.
123func (r *Require) setIndirect(indirect bool) {
124	r.Indirect = indirect
125	line := r.Syntax
126	if isIndirect(line) == indirect {
127		return
128	}
129	if indirect {
130		// Adding comment.
131		if len(line.Suffix) == 0 {
132			// New comment.
133			line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
134			return
135		}
136
137		com := &line.Suffix[0]
138		text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash)))
139		if text == "" {
140			// Empty comment.
141			com.Token = "// indirect"
142			return
143		}
144
145		// Insert at beginning of existing comment.
146		com.Token = "// indirect; " + text
147		return
148	}
149
150	// Removing comment.
151	f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
152	if f == "indirect" {
153		// Remove whole comment.
154		line.Suffix = nil
155		return
156	}
157
158	// Remove comment prefix.
159	com := &line.Suffix[0]
160	i := strings.Index(com.Token, "indirect;")
161	com.Token = "//" + com.Token[i+len("indirect;"):]
162}
163
164// isIndirect reports whether line has a "// indirect" comment,
165// meaning it is in go.mod only for its effect on indirect dependencies,
166// so that it can be dropped entirely once the effective version of the
167// indirect dependency reaches the given minimum version.
168func isIndirect(line *Line) bool {
169	if len(line.Suffix) == 0 {
170		return false
171	}
172	f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
173	return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;")
174}
175
176func (f *File) AddModuleStmt(path string) error {
177	if f.Syntax == nil {
178		f.Syntax = new(FileSyntax)
179	}
180	if f.Module == nil {
181		f.Module = &Module{
182			Mod:    module.Version{Path: path},
183			Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
184		}
185	} else {
186		f.Module.Mod.Path = path
187		f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
188	}
189	return nil
190}
191
192func (f *File) AddComment(text string) {
193	if f.Syntax == nil {
194		f.Syntax = new(FileSyntax)
195	}
196	f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
197		Comments: Comments{
198			Before: []Comment{
199				{
200					Token: text,
201				},
202			},
203		},
204	})
205}
206
207type VersionFixer func(path, version string) (string, error)
208
209// errDontFix is returned by a VersionFixer to indicate the version should be
210// left alone, even if it's not canonical.
211var dontFixRetract VersionFixer = func(_, vers string) (string, error) {
212	return vers, nil
213}
214
215// Parse parses and returns a go.mod file.
216//
217// file is the name of the file, used in positions and errors.
218//
219// data is the content of the file.
220//
221// fix is an optional function that canonicalizes module versions.
222// If fix is nil, all module versions must be canonical (module.CanonicalVersion
223// must return the same string).
224func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
225	return parseToFile(file, data, fix, true)
226}
227
228// ParseLax is like Parse but ignores unknown statements.
229// It is used when parsing go.mod files other than the main module,
230// under the theory that most statement types we add in the future will
231// only apply in the main module, like exclude and replace,
232// and so we get better gradual deployments if old go commands
233// simply ignore those statements when found in go.mod files
234// in dependencies.
235func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
236	return parseToFile(file, data, fix, false)
237}
238
239func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) {
240	fs, err := parse(file, data)
241	if err != nil {
242		return nil, err
243	}
244	f := &File{
245		Syntax: fs,
246	}
247	var errs ErrorList
248
249	// fix versions in retract directives after the file is parsed.
250	// We need the module path to fix versions, and it might be at the end.
251	defer func() {
252		oldLen := len(errs)
253		f.fixRetract(fix, &errs)
254		if len(errs) > oldLen {
255			parsed, err = nil, errs
256		}
257	}()
258
259	for _, x := range fs.Stmt {
260		switch x := x.(type) {
261		case *Line:
262			f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
263
264		case *LineBlock:
265			if len(x.Token) > 1 {
266				if strict {
267					errs = append(errs, Error{
268						Filename: file,
269						Pos:      x.Start,
270						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
271					})
272				}
273				continue
274			}
275			switch x.Token[0] {
276			default:
277				if strict {
278					errs = append(errs, Error{
279						Filename: file,
280						Pos:      x.Start,
281						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
282					})
283				}
284				continue
285			case "module", "require", "exclude", "replace", "retract":
286				for _, l := range x.Line {
287					f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
288				}
289			}
290		}
291	}
292
293	if len(errs) > 0 {
294		return nil, errs
295	}
296	return f, nil
297}
298
299var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
300var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
301
302func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
303	// If strict is false, this module is a dependency.
304	// We ignore all unknown directives as well as main-module-only
305	// directives like replace and exclude. It will work better for
306	// forward compatibility if we can depend on modules that have unknown
307	// statements (presumed relevant only when acting as the main module)
308	// and simply ignore those statements.
309	if !strict {
310		switch verb {
311		case "go", "module", "retract", "require":
312			// want these even for dependency go.mods
313		default:
314			return
315		}
316	}
317
318	wrapModPathError := func(modPath string, err error) {
319		*errs = append(*errs, Error{
320			Filename: f.Syntax.Name,
321			Pos:      line.Start,
322			ModPath:  modPath,
323			Verb:     verb,
324			Err:      err,
325		})
326	}
327	wrapError := func(err error) {
328		*errs = append(*errs, Error{
329			Filename: f.Syntax.Name,
330			Pos:      line.Start,
331			Err:      err,
332		})
333	}
334	errorf := func(format string, args ...interface{}) {
335		wrapError(fmt.Errorf(format, args...))
336	}
337
338	switch verb {
339	default:
340		errorf("unknown directive: %s", verb)
341
342	case "go":
343		if f.Go != nil {
344			errorf("repeated go statement")
345			return
346		}
347		if len(args) != 1 {
348			errorf("go directive expects exactly one argument")
349			return
350		} else if !GoVersionRE.MatchString(args[0]) {
351			fixed := false
352			if !strict {
353				if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil {
354					args[0] = m[1]
355					fixed = true
356				}
357			}
358			if !fixed {
359				errorf("invalid go version '%s': must match format 1.23", args[0])
360				return
361			}
362		}
363
364		f.Go = &Go{Syntax: line}
365		f.Go.Version = args[0]
366
367	case "module":
368		if f.Module != nil {
369			errorf("repeated module statement")
370			return
371		}
372		deprecated := parseDeprecation(block, line)
373		f.Module = &Module{
374			Syntax:     line,
375			Deprecated: deprecated,
376		}
377		if len(args) != 1 {
378			errorf("usage: module module/path")
379			return
380		}
381		s, err := parseString(&args[0])
382		if err != nil {
383			errorf("invalid quoted string: %v", err)
384			return
385		}
386		f.Module.Mod = module.Version{Path: s}
387
388	case "require", "exclude":
389		if len(args) != 2 {
390			errorf("usage: %s module/path v1.2.3", verb)
391			return
392		}
393		s, err := parseString(&args[0])
394		if err != nil {
395			errorf("invalid quoted string: %v", err)
396			return
397		}
398		v, err := parseVersion(verb, s, &args[1], fix)
399		if err != nil {
400			wrapError(err)
401			return
402		}
403		pathMajor, err := modulePathMajor(s)
404		if err != nil {
405			wrapError(err)
406			return
407		}
408		if err := module.CheckPathMajor(v, pathMajor); err != nil {
409			wrapModPathError(s, err)
410			return
411		}
412		if verb == "require" {
413			f.Require = append(f.Require, &Require{
414				Mod:      module.Version{Path: s, Version: v},
415				Syntax:   line,
416				Indirect: isIndirect(line),
417			})
418		} else {
419			f.Exclude = append(f.Exclude, &Exclude{
420				Mod:    module.Version{Path: s, Version: v},
421				Syntax: line,
422			})
423		}
424
425	case "replace":
426		arrow := 2
427		if len(args) >= 2 && args[1] == "=>" {
428			arrow = 1
429		}
430		if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
431			errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
432			return
433		}
434		s, err := parseString(&args[0])
435		if err != nil {
436			errorf("invalid quoted string: %v", err)
437			return
438		}
439		pathMajor, err := modulePathMajor(s)
440		if err != nil {
441			wrapModPathError(s, err)
442			return
443		}
444		var v string
445		if arrow == 2 {
446			v, err = parseVersion(verb, s, &args[1], fix)
447			if err != nil {
448				wrapError(err)
449				return
450			}
451			if err := module.CheckPathMajor(v, pathMajor); err != nil {
452				wrapModPathError(s, err)
453				return
454			}
455		}
456		ns, err := parseString(&args[arrow+1])
457		if err != nil {
458			errorf("invalid quoted string: %v", err)
459			return
460		}
461		nv := ""
462		if len(args) == arrow+2 {
463			if !IsDirectoryPath(ns) {
464				errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
465				return
466			}
467			if filepath.Separator == '/' && strings.Contains(ns, `\`) {
468				errorf("replacement directory appears to be Windows path (on a non-windows system)")
469				return
470			}
471		}
472		if len(args) == arrow+3 {
473			nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
474			if err != nil {
475				wrapError(err)
476				return
477			}
478			if IsDirectoryPath(ns) {
479				errorf("replacement module directory path %q cannot have version", ns)
480				return
481			}
482		}
483		f.Replace = append(f.Replace, &Replace{
484			Old:    module.Version{Path: s, Version: v},
485			New:    module.Version{Path: ns, Version: nv},
486			Syntax: line,
487		})
488
489	case "retract":
490		rationale := parseDirectiveComment(block, line)
491		vi, err := parseVersionInterval(verb, "", &args, dontFixRetract)
492		if err != nil {
493			if strict {
494				wrapError(err)
495				return
496			} else {
497				// Only report errors parsing intervals in the main module. We may
498				// support additional syntax in the future, such as open and half-open
499				// intervals. Those can't be supported now, because they break the
500				// go.mod parser, even in lax mode.
501				return
502			}
503		}
504		if len(args) > 0 && strict {
505			// In the future, there may be additional information after the version.
506			errorf("unexpected token after version: %q", args[0])
507			return
508		}
509		retract := &Retract{
510			VersionInterval: vi,
511			Rationale:       rationale,
512			Syntax:          line,
513		}
514		f.Retract = append(f.Retract, retract)
515	}
516}
517
518// fixRetract applies fix to each retract directive in f, appending any errors
519// to errs.
520//
521// Most versions are fixed as we parse the file, but for retract directives,
522// the relevant module path is the one specified with the module directive,
523// and that might appear at the end of the file (or not at all).
524func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
525	if fix == nil {
526		return
527	}
528	path := ""
529	if f.Module != nil {
530		path = f.Module.Mod.Path
531	}
532	var r *Retract
533	wrapError := func(err error) {
534		*errs = append(*errs, Error{
535			Filename: f.Syntax.Name,
536			Pos:      r.Syntax.Start,
537			Err:      err,
538		})
539	}
540
541	for _, r = range f.Retract {
542		if path == "" {
543			wrapError(errors.New("no module directive found, so retract cannot be used"))
544			return // only print the first one of these
545		}
546
547		args := r.Syntax.Token
548		if args[0] == "retract" {
549			args = args[1:]
550		}
551		vi, err := parseVersionInterval("retract", path, &args, fix)
552		if err != nil {
553			wrapError(err)
554		}
555		r.VersionInterval = vi
556	}
557}
558
559// IsDirectoryPath reports whether the given path should be interpreted
560// as a directory path. Just like on the go command line, relative paths
561// and rooted paths are directory paths; the rest are module paths.
562func IsDirectoryPath(ns string) bool {
563	// Because go.mod files can move from one system to another,
564	// we check all known path syntaxes, both Unix and Windows.
565	return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
566		strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) ||
567		len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
568}
569
570// MustQuote reports whether s must be quoted in order to appear as
571// a single token in a go.mod line.
572func MustQuote(s string) bool {
573	for _, r := range s {
574		switch r {
575		case ' ', '"', '\'', '`':
576			return true
577
578		case '(', ')', '[', ']', '{', '}', ',':
579			if len(s) > 1 {
580				return true
581			}
582
583		default:
584			if !unicode.IsPrint(r) {
585				return true
586			}
587		}
588	}
589	return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
590}
591
592// AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
593// the quotation of s.
594func AutoQuote(s string) string {
595	if MustQuote(s) {
596		return strconv.Quote(s)
597	}
598	return s
599}
600
601func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) {
602	toks := *args
603	if len(toks) == 0 || toks[0] == "(" {
604		return VersionInterval{}, fmt.Errorf("expected '[' or version")
605	}
606	if toks[0] != "[" {
607		v, err := parseVersion(verb, path, &toks[0], fix)
608		if err != nil {
609			return VersionInterval{}, err
610		}
611		*args = toks[1:]
612		return VersionInterval{Low: v, High: v}, nil
613	}
614	toks = toks[1:]
615
616	if len(toks) == 0 {
617		return VersionInterval{}, fmt.Errorf("expected version after '['")
618	}
619	low, err := parseVersion(verb, path, &toks[0], fix)
620	if err != nil {
621		return VersionInterval{}, err
622	}
623	toks = toks[1:]
624
625	if len(toks) == 0 || toks[0] != "," {
626		return VersionInterval{}, fmt.Errorf("expected ',' after version")
627	}
628	toks = toks[1:]
629
630	if len(toks) == 0 {
631		return VersionInterval{}, fmt.Errorf("expected version after ','")
632	}
633	high, err := parseVersion(verb, path, &toks[0], fix)
634	if err != nil {
635		return VersionInterval{}, err
636	}
637	toks = toks[1:]
638
639	if len(toks) == 0 || toks[0] != "]" {
640		return VersionInterval{}, fmt.Errorf("expected ']' after version")
641	}
642	toks = toks[1:]
643
644	*args = toks
645	return VersionInterval{Low: low, High: high}, nil
646}
647
648func parseString(s *string) (string, error) {
649	t := *s
650	if strings.HasPrefix(t, `"`) {
651		var err error
652		if t, err = strconv.Unquote(t); err != nil {
653			return "", err
654		}
655	} else if strings.ContainsAny(t, "\"'`") {
656		// Other quotes are reserved both for possible future expansion
657		// and to avoid confusion. For example if someone types 'x'
658		// we want that to be a syntax error and not a literal x in literal quotation marks.
659		return "", fmt.Errorf("unquoted string cannot contain quote")
660	}
661	*s = AutoQuote(t)
662	return t, nil
663}
664
665var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`)
666
667// parseDeprecation extracts the text of comments on a "module" directive and
668// extracts a deprecation message from that.
669//
670// A deprecation message is contained in a paragraph within a block of comments
671// that starts with "Deprecated:" (case sensitive). The message runs until the
672// end of the paragraph and does not include the "Deprecated:" prefix. If the
673// comment block has multiple paragraphs that start with "Deprecated:",
674// parseDeprecation returns the message from the first.
675func parseDeprecation(block *LineBlock, line *Line) string {
676	text := parseDirectiveComment(block, line)
677	m := deprecatedRE.FindStringSubmatch(text)
678	if m == nil {
679		return ""
680	}
681	return m[1]
682}
683
684// parseDirectiveComment extracts the text of comments on a directive.
685// If the directive's line does not have comments and is part of a block that
686// does have comments, the block's comments are used.
687func parseDirectiveComment(block *LineBlock, line *Line) string {
688	comments := line.Comment()
689	if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
690		comments = block.Comment()
691	}
692	groups := [][]Comment{comments.Before, comments.Suffix}
693	var lines []string
694	for _, g := range groups {
695		for _, c := range g {
696			if !strings.HasPrefix(c.Token, "//") {
697				continue // blank line
698			}
699			lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
700		}
701	}
702	return strings.Join(lines, "\n")
703}
704
705type ErrorList []Error
706
707func (e ErrorList) Error() string {
708	errStrs := make([]string, len(e))
709	for i, err := range e {
710		errStrs[i] = err.Error()
711	}
712	return strings.Join(errStrs, "\n")
713}
714
715type Error struct {
716	Filename string
717	Pos      Position
718	Verb     string
719	ModPath  string
720	Err      error
721}
722
723func (e *Error) Error() string {
724	var pos string
725	if e.Pos.LineRune > 1 {
726		// Don't print LineRune if it's 1 (beginning of line).
727		// It's always 1 except in scanner errors, which are rare.
728		pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune)
729	} else if e.Pos.Line > 0 {
730		pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line)
731	} else if e.Filename != "" {
732		pos = fmt.Sprintf("%s: ", e.Filename)
733	}
734
735	var directive string
736	if e.ModPath != "" {
737		directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
738	} else if e.Verb != "" {
739		directive = fmt.Sprintf("%s: ", e.Verb)
740	}
741
742	return pos + directive + e.Err.Error()
743}
744
745func (e *Error) Unwrap() error { return e.Err }
746
747func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
748	t, err := parseString(s)
749	if err != nil {
750		return "", &Error{
751			Verb:    verb,
752			ModPath: path,
753			Err: &module.InvalidVersionError{
754				Version: *s,
755				Err:     err,
756			},
757		}
758	}
759	if fix != nil {
760		fixed, err := fix(path, t)
761		if err != nil {
762			if err, ok := err.(*module.ModuleError); ok {
763				return "", &Error{
764					Verb:    verb,
765					ModPath: path,
766					Err:     err.Err,
767				}
768			}
769			return "", err
770		}
771		t = fixed
772	} else {
773		cv := module.CanonicalVersion(t)
774		if cv == "" {
775			return "", &Error{
776				Verb:    verb,
777				ModPath: path,
778				Err: &module.InvalidVersionError{
779					Version: t,
780					Err:     errors.New("must be of the form v1.2.3"),
781				},
782			}
783		}
784		t = cv
785	}
786	*s = t
787	return *s, nil
788}
789
790func modulePathMajor(path string) (string, error) {
791	_, major, ok := module.SplitPathVersion(path)
792	if !ok {
793		return "", fmt.Errorf("invalid module path")
794	}
795	return major, nil
796}
797
798func (f *File) Format() ([]byte, error) {
799	return Format(f.Syntax), nil
800}
801
802// Cleanup cleans up the file f after any edit operations.
803// To avoid quadratic behavior, modifications like DropRequire
804// clear the entry but do not remove it from the slice.
805// Cleanup cleans out all the cleared entries.
806func (f *File) Cleanup() {
807	w := 0
808	for _, r := range f.Require {
809		if r.Mod.Path != "" {
810			f.Require[w] = r
811			w++
812		}
813	}
814	f.Require = f.Require[:w]
815
816	w = 0
817	for _, x := range f.Exclude {
818		if x.Mod.Path != "" {
819			f.Exclude[w] = x
820			w++
821		}
822	}
823	f.Exclude = f.Exclude[:w]
824
825	w = 0
826	for _, r := range f.Replace {
827		if r.Old.Path != "" {
828			f.Replace[w] = r
829			w++
830		}
831	}
832	f.Replace = f.Replace[:w]
833
834	w = 0
835	for _, r := range f.Retract {
836		if r.Low != "" || r.High != "" {
837			f.Retract[w] = r
838			w++
839		}
840	}
841	f.Retract = f.Retract[:w]
842
843	f.Syntax.Cleanup()
844}
845
846func (f *File) AddGoStmt(version string) error {
847	if !GoVersionRE.MatchString(version) {
848		return fmt.Errorf("invalid language version string %q", version)
849	}
850	if f.Go == nil {
851		var hint Expr
852		if f.Module != nil && f.Module.Syntax != nil {
853			hint = f.Module.Syntax
854		}
855		f.Go = &Go{
856			Version: version,
857			Syntax:  f.Syntax.addLine(hint, "go", version),
858		}
859	} else {
860		f.Go.Version = version
861		f.Syntax.updateLine(f.Go.Syntax, "go", version)
862	}
863	return nil
864}
865
866// AddRequire sets the first require line for path to version vers,
867// preserving any existing comments for that line and removing all
868// other lines for path.
869//
870// If no line currently exists for path, AddRequire adds a new line
871// at the end of the last require block.
872func (f *File) AddRequire(path, vers string) error {
873	need := true
874	for _, r := range f.Require {
875		if r.Mod.Path == path {
876			if need {
877				r.Mod.Version = vers
878				f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
879				need = false
880			} else {
881				r.Syntax.markRemoved()
882				*r = Require{}
883			}
884		}
885	}
886
887	if need {
888		f.AddNewRequire(path, vers, false)
889	}
890	return nil
891}
892
893// AddNewRequire adds a new require line for path at version vers at the end of
894// the last require block, regardless of any existing require lines for path.
895func (f *File) AddNewRequire(path, vers string, indirect bool) {
896	line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
897	r := &Require{
898		Mod:    module.Version{Path: path, Version: vers},
899		Syntax: line,
900	}
901	r.setIndirect(indirect)
902	f.Require = append(f.Require, r)
903}
904
905// SetRequire updates the requirements of f to contain exactly req, preserving
906// the existing block structure and line comment contents (except for 'indirect'
907// markings) for the first requirement on each named module path.
908//
909// The Syntax field is ignored for the requirements in req.
910//
911// Any requirements not already present in the file are added to the block
912// containing the last require line.
913//
914// The requirements in req must specify at most one distinct version for each
915// module path.
916//
917// If any existing requirements may be removed, the caller should call Cleanup
918// after all edits are complete.
919func (f *File) SetRequire(req []*Require) {
920	type elem struct {
921		version  string
922		indirect bool
923	}
924	need := make(map[string]elem)
925	for _, r := range req {
926		if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version {
927			panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version))
928		}
929		need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect}
930	}
931
932	// Update or delete the existing Require entries to preserve
933	// only the first for each module path in req.
934	for _, r := range f.Require {
935		e, ok := need[r.Mod.Path]
936		if ok {
937			r.setVersion(e.version)
938			r.setIndirect(e.indirect)
939		} else {
940			r.markRemoved()
941		}
942		delete(need, r.Mod.Path)
943	}
944
945	// Add new entries in the last block of the file for any paths that weren't
946	// already present.
947	//
948	// This step is nondeterministic, but the final result will be deterministic
949	// because we will sort the block.
950	for path, e := range need {
951		f.AddNewRequire(path, e.version, e.indirect)
952	}
953
954	f.SortBlocks()
955}
956
957// SetRequireSeparateIndirect updates the requirements of f to contain the given
958// requirements. Comment contents (except for 'indirect' markings) are retained
959// from the first existing requirement for each module path, and block structure
960// is maintained as long as the indirect markings match.
961//
962// Any requirements on paths not already present in the file are added. Direct
963// requirements are added to the last block containing *any* other direct
964// requirement. Indirect requirements are added to the last block containing
965// *only* other indirect requirements. If no suitable block exists, a new one is
966// added, with the last block containing a direct dependency (if any)
967// immediately before the first block containing only indirect dependencies.
968//
969// The Syntax field is ignored for requirements in the given blocks.
970func (f *File) SetRequireSeparateIndirect(req []*Require) {
971	type modKey struct {
972		path     string
973		indirect bool
974	}
975	need := make(map[modKey]string)
976	for _, r := range req {
977		need[modKey{r.Mod.Path, r.Indirect}] = r.Mod.Version
978	}
979
980	comments := make(map[string]Comments)
981	for _, r := range f.Require {
982		v, ok := need[modKey{r.Mod.Path, r.Indirect}]
983		if !ok {
984			if _, ok := need[modKey{r.Mod.Path, !r.Indirect}]; ok {
985				if _, dup := comments[r.Mod.Path]; !dup {
986					comments[r.Mod.Path] = r.Syntax.Comments
987				}
988			}
989			r.markRemoved()
990			continue
991		}
992		r.setVersion(v)
993		delete(need, modKey{r.Mod.Path, r.Indirect})
994	}
995
996	var (
997		lastDirectOrMixedBlock Expr
998		firstIndirectOnlyBlock Expr
999		lastIndirectOnlyBlock  Expr
1000	)
1001	for _, stmt := range f.Syntax.Stmt {
1002		switch stmt := stmt.(type) {
1003		case *Line:
1004			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
1005				continue
1006			}
1007			if isIndirect(stmt) {
1008				lastIndirectOnlyBlock = stmt
1009			} else {
1010				lastDirectOrMixedBlock = stmt
1011			}
1012		case *LineBlock:
1013			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
1014				continue
1015			}
1016			indirectOnly := true
1017			for _, line := range stmt.Line {
1018				if len(line.Token) == 0 {
1019					continue
1020				}
1021				if !isIndirect(line) {
1022					indirectOnly = false
1023					break
1024				}
1025			}
1026			if indirectOnly {
1027				lastIndirectOnlyBlock = stmt
1028				if firstIndirectOnlyBlock == nil {
1029					firstIndirectOnlyBlock = stmt
1030				}
1031			} else {
1032				lastDirectOrMixedBlock = stmt
1033			}
1034		}
1035	}
1036
1037	isOrContainsStmt := func(stmt Expr, target Expr) bool {
1038		if stmt == target {
1039			return true
1040		}
1041		if stmt, ok := stmt.(*LineBlock); ok {
1042			if target, ok := target.(*Line); ok {
1043				for _, line := range stmt.Line {
1044					if line == target {
1045						return true
1046					}
1047				}
1048			}
1049		}
1050		return false
1051	}
1052
1053	addRequire := func(path, vers string, indirect bool, comments Comments) {
1054		var line *Line
1055		if indirect {
1056			if lastIndirectOnlyBlock != nil {
1057				line = f.Syntax.addLine(lastIndirectOnlyBlock, "require", path, vers)
1058			} else {
1059				// Add a new require block after the last direct-only or mixed "require"
1060				// block (if any).
1061				//
1062				// (f.Syntax.addLine would add the line to an existing "require" block if
1063				// present, but here the existing "require" blocks are all direct-only, so
1064				// we know we need to add a new block instead.)
1065				line = &Line{Token: []string{"require", path, vers}}
1066				lastIndirectOnlyBlock = line
1067				firstIndirectOnlyBlock = line // only block implies first block
1068				if lastDirectOrMixedBlock == nil {
1069					f.Syntax.Stmt = append(f.Syntax.Stmt, line)
1070				} else {
1071					for i, stmt := range f.Syntax.Stmt {
1072						if isOrContainsStmt(stmt, lastDirectOrMixedBlock) {
1073							f.Syntax.Stmt = append(f.Syntax.Stmt, nil)     // increase size
1074							copy(f.Syntax.Stmt[i+2:], f.Syntax.Stmt[i+1:]) // shuffle elements up
1075							f.Syntax.Stmt[i+1] = line
1076							break
1077						}
1078					}
1079				}
1080			}
1081		} else {
1082			if lastDirectOrMixedBlock != nil {
1083				line = f.Syntax.addLine(lastDirectOrMixedBlock, "require", path, vers)
1084			} else {
1085				// Add a new require block before the first indirect block (if any).
1086				//
1087				// That way if the file initially contains only indirect lines,
1088				// the direct lines still appear before it: we preserve existing
1089				// structure, but only to the extent that that structure already
1090				// reflects the direct/indirect split.
1091				line = &Line{Token: []string{"require", path, vers}}
1092				lastDirectOrMixedBlock = line
1093				if firstIndirectOnlyBlock == nil {
1094					f.Syntax.Stmt = append(f.Syntax.Stmt, line)
1095				} else {
1096					for i, stmt := range f.Syntax.Stmt {
1097						if isOrContainsStmt(stmt, firstIndirectOnlyBlock) {
1098							f.Syntax.Stmt = append(f.Syntax.Stmt, nil)   // increase size
1099							copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:]) // shuffle elements up
1100							f.Syntax.Stmt[i] = line
1101							break
1102						}
1103					}
1104				}
1105			}
1106		}
1107
1108		line.Comments.Before = commentsAdd(line.Comments.Before, comments.Before)
1109		line.Comments.Suffix = commentsAdd(line.Comments.Suffix, comments.Suffix)
1110
1111		r := &Require{
1112			Mod:      module.Version{Path: path, Version: vers},
1113			Indirect: indirect,
1114			Syntax:   line,
1115		}
1116		r.setIndirect(indirect)
1117		f.Require = append(f.Require, r)
1118	}
1119
1120	for k, vers := range need {
1121		addRequire(k.path, vers, k.indirect, comments[k.path])
1122	}
1123	f.SortBlocks()
1124}
1125
1126func (f *File) DropRequire(path string) error {
1127	for _, r := range f.Require {
1128		if r.Mod.Path == path {
1129			r.Syntax.markRemoved()
1130			*r = Require{}
1131		}
1132	}
1133	return nil
1134}
1135
1136// AddExclude adds a exclude statement to the mod file. Errors if the provided
1137// version is not a canonical version string
1138func (f *File) AddExclude(path, vers string) error {
1139	if err := checkCanonicalVersion(path, vers); err != nil {
1140		return err
1141	}
1142
1143	var hint *Line
1144	for _, x := range f.Exclude {
1145		if x.Mod.Path == path && x.Mod.Version == vers {
1146			return nil
1147		}
1148		if x.Mod.Path == path {
1149			hint = x.Syntax
1150		}
1151	}
1152
1153	f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
1154	return nil
1155}
1156
1157func (f *File) DropExclude(path, vers string) error {
1158	for _, x := range f.Exclude {
1159		if x.Mod.Path == path && x.Mod.Version == vers {
1160			x.Syntax.markRemoved()
1161			*x = Exclude{}
1162		}
1163	}
1164	return nil
1165}
1166
1167func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
1168	need := true
1169	old := module.Version{Path: oldPath, Version: oldVers}
1170	new := module.Version{Path: newPath, Version: newVers}
1171	tokens := []string{"replace", AutoQuote(oldPath)}
1172	if oldVers != "" {
1173		tokens = append(tokens, oldVers)
1174	}
1175	tokens = append(tokens, "=>", AutoQuote(newPath))
1176	if newVers != "" {
1177		tokens = append(tokens, newVers)
1178	}
1179
1180	var hint *Line
1181	for _, r := range f.Replace {
1182		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
1183			if need {
1184				// Found replacement for old; update to use new.
1185				r.New = new
1186				f.Syntax.updateLine(r.Syntax, tokens...)
1187				need = false
1188				continue
1189			}
1190			// Already added; delete other replacements for same.
1191			r.Syntax.markRemoved()
1192			*r = Replace{}
1193		}
1194		if r.Old.Path == oldPath {
1195			hint = r.Syntax
1196		}
1197	}
1198	if need {
1199		f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
1200	}
1201	return nil
1202}
1203
1204func (f *File) DropReplace(oldPath, oldVers string) error {
1205	for _, r := range f.Replace {
1206		if r.Old.Path == oldPath && r.Old.Version == oldVers {
1207			r.Syntax.markRemoved()
1208			*r = Replace{}
1209		}
1210	}
1211	return nil
1212}
1213
1214// AddRetract adds a retract statement to the mod file. Errors if the provided
1215// version interval does not consist of canonical version strings
1216func (f *File) AddRetract(vi VersionInterval, rationale string) error {
1217	var path string
1218	if f.Module != nil {
1219		path = f.Module.Mod.Path
1220	}
1221	if err := checkCanonicalVersion(path, vi.High); err != nil {
1222		return err
1223	}
1224	if err := checkCanonicalVersion(path, vi.Low); err != nil {
1225		return err
1226	}
1227
1228	r := &Retract{
1229		VersionInterval: vi,
1230	}
1231	if vi.Low == vi.High {
1232		r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
1233	} else {
1234		r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
1235	}
1236	if rationale != "" {
1237		for _, line := range strings.Split(rationale, "\n") {
1238			com := Comment{Token: "// " + line}
1239			r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
1240		}
1241	}
1242	return nil
1243}
1244
1245func (f *File) DropRetract(vi VersionInterval) error {
1246	for _, r := range f.Retract {
1247		if r.VersionInterval == vi {
1248			r.Syntax.markRemoved()
1249			*r = Retract{}
1250		}
1251	}
1252	return nil
1253}
1254
1255func (f *File) SortBlocks() {
1256	f.removeDups() // otherwise sorting is unsafe
1257
1258	for _, stmt := range f.Syntax.Stmt {
1259		block, ok := stmt.(*LineBlock)
1260		if !ok {
1261			continue
1262		}
1263		less := lineLess
1264		if block.Token[0] == "retract" {
1265			less = lineRetractLess
1266		}
1267		sort.SliceStable(block.Line, func(i, j int) bool {
1268			return less(block.Line[i], block.Line[j])
1269		})
1270	}
1271}
1272
1273// removeDups removes duplicate exclude and replace directives.
1274//
1275// Earlier exclude directives take priority.
1276//
1277// Later replace directives take priority.
1278//
1279// require directives are not de-duplicated. That's left up to higher-level
1280// logic (MVS).
1281//
1282// retract directives are not de-duplicated since comments are
1283// meaningful, and versions may be retracted multiple times.
1284func (f *File) removeDups() {
1285	kill := make(map[*Line]bool)
1286
1287	// Remove duplicate excludes.
1288	haveExclude := make(map[module.Version]bool)
1289	for _, x := range f.Exclude {
1290		if haveExclude[x.Mod] {
1291			kill[x.Syntax] = true
1292			continue
1293		}
1294		haveExclude[x.Mod] = true
1295	}
1296	var excl []*Exclude
1297	for _, x := range f.Exclude {
1298		if !kill[x.Syntax] {
1299			excl = append(excl, x)
1300		}
1301	}
1302	f.Exclude = excl
1303
1304	// Remove duplicate replacements.
1305	// Later replacements take priority over earlier ones.
1306	haveReplace := make(map[module.Version]bool)
1307	for i := len(f.Replace) - 1; i >= 0; i-- {
1308		x := f.Replace[i]
1309		if haveReplace[x.Old] {
1310			kill[x.Syntax] = true
1311			continue
1312		}
1313		haveReplace[x.Old] = true
1314	}
1315	var repl []*Replace
1316	for _, x := range f.Replace {
1317		if !kill[x.Syntax] {
1318			repl = append(repl, x)
1319		}
1320	}
1321	f.Replace = repl
1322
1323	// Duplicate require and retract directives are not removed.
1324
1325	// Drop killed statements from the syntax tree.
1326	var stmts []Expr
1327	for _, stmt := range f.Syntax.Stmt {
1328		switch stmt := stmt.(type) {
1329		case *Line:
1330			if kill[stmt] {
1331				continue
1332			}
1333		case *LineBlock:
1334			var lines []*Line
1335			for _, line := range stmt.Line {
1336				if !kill[line] {
1337					lines = append(lines, line)
1338				}
1339			}
1340			stmt.Line = lines
1341			if len(lines) == 0 {
1342				continue
1343			}
1344		}
1345		stmts = append(stmts, stmt)
1346	}
1347	f.Syntax.Stmt = stmts
1348}
1349
1350// lineLess returns whether li should be sorted before lj. It sorts
1351// lexicographically without assigning any special meaning to tokens.
1352func lineLess(li, lj *Line) bool {
1353	for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
1354		if li.Token[k] != lj.Token[k] {
1355			return li.Token[k] < lj.Token[k]
1356		}
1357	}
1358	return len(li.Token) < len(lj.Token)
1359}
1360
1361// lineRetractLess returns whether li should be sorted before lj for lines in
1362// a "retract" block. It treats each line as a version interval. Single versions
1363// are compared as if they were intervals with the same low and high version.
1364// Intervals are sorted in descending order, first by low version, then by
1365// high version, using semver.Compare.
1366func lineRetractLess(li, lj *Line) bool {
1367	interval := func(l *Line) VersionInterval {
1368		if len(l.Token) == 1 {
1369			return VersionInterval{Low: l.Token[0], High: l.Token[0]}
1370		} else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
1371			return VersionInterval{Low: l.Token[1], High: l.Token[3]}
1372		} else {
1373			// Line in unknown format. Treat as an invalid version.
1374			return VersionInterval{}
1375		}
1376	}
1377	vii := interval(li)
1378	vij := interval(lj)
1379	if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
1380		return cmp > 0
1381	}
1382	return semver.Compare(vii.High, vij.High) > 0
1383}
1384
1385// checkCanonicalVersion returns a non-nil error if vers is not a canonical
1386// version string or does not match the major version of path.
1387//
1388// If path is non-empty, the error text suggests a format with a major version
1389// corresponding to the path.
1390func checkCanonicalVersion(path, vers string) error {
1391	_, pathMajor, pathMajorOk := module.SplitPathVersion(path)
1392
1393	if vers == "" || vers != module.CanonicalVersion(vers) {
1394		if pathMajor == "" {
1395			return &module.InvalidVersionError{
1396				Version: vers,
1397				Err:     fmt.Errorf("must be of the form v1.2.3"),
1398			}
1399		}
1400		return &module.InvalidVersionError{
1401			Version: vers,
1402			Err:     fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)),
1403		}
1404	}
1405
1406	if pathMajorOk {
1407		if err := module.CheckPathMajor(vers, pathMajor); err != nil {
1408			if pathMajor == "" {
1409				// In this context, the user probably wrote "v2.3.4" when they meant
1410				// "v2.3.4+incompatible". Suggest that instead of "v0 or v1".
1411				return &module.InvalidVersionError{
1412					Version: vers,
1413					Err:     fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)),
1414				}
1415			}
1416			return err
1417		}
1418	}
1419
1420	return nil
1421}
1422