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://pkg.go.dev/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 [File.AddNewRequire] and
16// [File.DropReplace] 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	Toolchain *Toolchain
41	Godebug   []*Godebug
42	Require   []*Require
43	Exclude   []*Exclude
44	Replace   []*Replace
45	Retract   []*Retract
46
47	Syntax *FileSyntax
48}
49
50// A Module is the module statement.
51type Module struct {
52	Mod        module.Version
53	Deprecated string
54	Syntax     *Line
55}
56
57// A Go is the go statement.
58type Go struct {
59	Version string // "1.23"
60	Syntax  *Line
61}
62
63// A Toolchain is the toolchain statement.
64type Toolchain struct {
65	Name   string // "go1.21rc1"
66	Syntax *Line
67}
68
69// A Godebug is a single godebug key=value statement.
70type Godebug struct {
71	Key    string
72	Value  string
73	Syntax *Line
74}
75
76// An Exclude is a single exclude statement.
77type Exclude struct {
78	Mod    module.Version
79	Syntax *Line
80}
81
82// A Replace is a single replace statement.
83type Replace struct {
84	Old    module.Version
85	New    module.Version
86	Syntax *Line
87}
88
89// A Retract is a single retract statement.
90type Retract struct {
91	VersionInterval
92	Rationale string
93	Syntax    *Line
94}
95
96// A VersionInterval represents a range of versions with upper and lower bounds.
97// Intervals are closed: both bounds are included. When Low is equal to High,
98// the interval may refer to a single version ('v1.2.3') or an interval
99// ('[v1.2.3, v1.2.3]'); both have the same representation.
100type VersionInterval struct {
101	Low, High string
102}
103
104// A Require is a single require statement.
105type Require struct {
106	Mod      module.Version
107	Indirect bool // has "// indirect" comment
108	Syntax   *Line
109}
110
111func (r *Require) markRemoved() {
112	r.Syntax.markRemoved()
113	*r = Require{}
114}
115
116func (r *Require) setVersion(v string) {
117	r.Mod.Version = v
118
119	if line := r.Syntax; len(line.Token) > 0 {
120		if line.InBlock {
121			// If the line is preceded by an empty line, remove it; see
122			// https://golang.org/issue/33779.
123			if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
124				line.Comments.Before = line.Comments.Before[:0]
125			}
126			if len(line.Token) >= 2 { // example.com v1.2.3
127				line.Token[1] = v
128			}
129		} else {
130			if len(line.Token) >= 3 { // require example.com v1.2.3
131				line.Token[2] = v
132			}
133		}
134	}
135}
136
137// setIndirect sets line to have (or not have) a "// indirect" comment.
138func (r *Require) setIndirect(indirect bool) {
139	r.Indirect = indirect
140	line := r.Syntax
141	if isIndirect(line) == indirect {
142		return
143	}
144	if indirect {
145		// Adding comment.
146		if len(line.Suffix) == 0 {
147			// New comment.
148			line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
149			return
150		}
151
152		com := &line.Suffix[0]
153		text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash)))
154		if text == "" {
155			// Empty comment.
156			com.Token = "// indirect"
157			return
158		}
159
160		// Insert at beginning of existing comment.
161		com.Token = "// indirect; " + text
162		return
163	}
164
165	// Removing comment.
166	f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
167	if f == "indirect" {
168		// Remove whole comment.
169		line.Suffix = nil
170		return
171	}
172
173	// Remove comment prefix.
174	com := &line.Suffix[0]
175	i := strings.Index(com.Token, "indirect;")
176	com.Token = "//" + com.Token[i+len("indirect;"):]
177}
178
179// isIndirect reports whether line has a "// indirect" comment,
180// meaning it is in go.mod only for its effect on indirect dependencies,
181// so that it can be dropped entirely once the effective version of the
182// indirect dependency reaches the given minimum version.
183func isIndirect(line *Line) bool {
184	if len(line.Suffix) == 0 {
185		return false
186	}
187	f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
188	return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;")
189}
190
191func (f *File) AddModuleStmt(path string) error {
192	if f.Syntax == nil {
193		f.Syntax = new(FileSyntax)
194	}
195	if f.Module == nil {
196		f.Module = &Module{
197			Mod:    module.Version{Path: path},
198			Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
199		}
200	} else {
201		f.Module.Mod.Path = path
202		f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
203	}
204	return nil
205}
206
207func (f *File) AddComment(text string) {
208	if f.Syntax == nil {
209		f.Syntax = new(FileSyntax)
210	}
211	f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
212		Comments: Comments{
213			Before: []Comment{
214				{
215					Token: text,
216				},
217			},
218		},
219	})
220}
221
222type VersionFixer func(path, version string) (string, error)
223
224// errDontFix is returned by a VersionFixer to indicate the version should be
225// left alone, even if it's not canonical.
226var dontFixRetract VersionFixer = func(_, vers string) (string, error) {
227	return vers, nil
228}
229
230// Parse parses and returns a go.mod file.
231//
232// file is the name of the file, used in positions and errors.
233//
234// data is the content of the file.
235//
236// fix is an optional function that canonicalizes module versions.
237// If fix is nil, all module versions must be canonical ([module.CanonicalVersion]
238// must return the same string).
239func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
240	return parseToFile(file, data, fix, true)
241}
242
243// ParseLax is like Parse but ignores unknown statements.
244// It is used when parsing go.mod files other than the main module,
245// under the theory that most statement types we add in the future will
246// only apply in the main module, like exclude and replace,
247// and so we get better gradual deployments if old go commands
248// simply ignore those statements when found in go.mod files
249// in dependencies.
250func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
251	return parseToFile(file, data, fix, false)
252}
253
254func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) {
255	fs, err := parse(file, data)
256	if err != nil {
257		return nil, err
258	}
259	f := &File{
260		Syntax: fs,
261	}
262	var errs ErrorList
263
264	// fix versions in retract directives after the file is parsed.
265	// We need the module path to fix versions, and it might be at the end.
266	defer func() {
267		oldLen := len(errs)
268		f.fixRetract(fix, &errs)
269		if len(errs) > oldLen {
270			parsed, err = nil, errs
271		}
272	}()
273
274	for _, x := range fs.Stmt {
275		switch x := x.(type) {
276		case *Line:
277			f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
278
279		case *LineBlock:
280			if len(x.Token) > 1 {
281				if strict {
282					errs = append(errs, Error{
283						Filename: file,
284						Pos:      x.Start,
285						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
286					})
287				}
288				continue
289			}
290			switch x.Token[0] {
291			default:
292				if strict {
293					errs = append(errs, Error{
294						Filename: file,
295						Pos:      x.Start,
296						Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
297					})
298				}
299				continue
300			case "module", "godebug", "require", "exclude", "replace", "retract":
301				for _, l := range x.Line {
302					f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
303				}
304			}
305		}
306	}
307
308	if len(errs) > 0 {
309		return nil, errs
310	}
311	return f, nil
312}
313
314var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)
315var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
316
317// Toolchains must be named beginning with `go1`,
318// like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted.
319// Note that this regexp is a much looser condition than go/version.IsValid,
320// for forward compatibility.
321// (This code has to be work to identify new toolchains even if we tweak the syntax in the future.)
322var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`)
323
324func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
325	// If strict is false, this module is a dependency.
326	// We ignore all unknown directives as well as main-module-only
327	// directives like replace and exclude. It will work better for
328	// forward compatibility if we can depend on modules that have unknown
329	// statements (presumed relevant only when acting as the main module)
330	// and simply ignore those statements.
331	if !strict {
332		switch verb {
333		case "go", "module", "retract", "require":
334			// want these even for dependency go.mods
335		default:
336			return
337		}
338	}
339
340	wrapModPathError := func(modPath string, err error) {
341		*errs = append(*errs, Error{
342			Filename: f.Syntax.Name,
343			Pos:      line.Start,
344			ModPath:  modPath,
345			Verb:     verb,
346			Err:      err,
347		})
348	}
349	wrapError := func(err error) {
350		*errs = append(*errs, Error{
351			Filename: f.Syntax.Name,
352			Pos:      line.Start,
353			Err:      err,
354		})
355	}
356	errorf := func(format string, args ...interface{}) {
357		wrapError(fmt.Errorf(format, args...))
358	}
359
360	switch verb {
361	default:
362		errorf("unknown directive: %s", verb)
363
364	case "go":
365		if f.Go != nil {
366			errorf("repeated go statement")
367			return
368		}
369		if len(args) != 1 {
370			errorf("go directive expects exactly one argument")
371			return
372		} else if !GoVersionRE.MatchString(args[0]) {
373			fixed := false
374			if !strict {
375				if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil {
376					args[0] = m[1]
377					fixed = true
378				}
379			}
380			if !fixed {
381				errorf("invalid go version '%s': must match format 1.23.0", args[0])
382				return
383			}
384		}
385
386		f.Go = &Go{Syntax: line}
387		f.Go.Version = args[0]
388
389	case "toolchain":
390		if f.Toolchain != nil {
391			errorf("repeated toolchain statement")
392			return
393		}
394		if len(args) != 1 {
395			errorf("toolchain directive expects exactly one argument")
396			return
397		} else if !ToolchainRE.MatchString(args[0]) {
398			errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0])
399			return
400		}
401		f.Toolchain = &Toolchain{Syntax: line}
402		f.Toolchain.Name = args[0]
403
404	case "module":
405		if f.Module != nil {
406			errorf("repeated module statement")
407			return
408		}
409		deprecated := parseDeprecation(block, line)
410		f.Module = &Module{
411			Syntax:     line,
412			Deprecated: deprecated,
413		}
414		if len(args) != 1 {
415			errorf("usage: module module/path")
416			return
417		}
418		s, err := parseString(&args[0])
419		if err != nil {
420			errorf("invalid quoted string: %v", err)
421			return
422		}
423		f.Module.Mod = module.Version{Path: s}
424
425	case "godebug":
426		if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") {
427			errorf("usage: godebug key=value")
428			return
429		}
430		key, value, ok := strings.Cut(args[0], "=")
431		if !ok {
432			errorf("usage: godebug key=value")
433			return
434		}
435		f.Godebug = append(f.Godebug, &Godebug{
436			Key:    key,
437			Value:  value,
438			Syntax: line,
439		})
440
441	case "require", "exclude":
442		if len(args) != 2 {
443			errorf("usage: %s module/path v1.2.3", verb)
444			return
445		}
446		s, err := parseString(&args[0])
447		if err != nil {
448			errorf("invalid quoted string: %v", err)
449			return
450		}
451		v, err := parseVersion(verb, s, &args[1], fix)
452		if err != nil {
453			wrapError(err)
454			return
455		}
456		pathMajor, err := modulePathMajor(s)
457		if err != nil {
458			wrapError(err)
459			return
460		}
461		if err := module.CheckPathMajor(v, pathMajor); err != nil {
462			wrapModPathError(s, err)
463			return
464		}
465		if verb == "require" {
466			f.Require = append(f.Require, &Require{
467				Mod:      module.Version{Path: s, Version: v},
468				Syntax:   line,
469				Indirect: isIndirect(line),
470			})
471		} else {
472			f.Exclude = append(f.Exclude, &Exclude{
473				Mod:    module.Version{Path: s, Version: v},
474				Syntax: line,
475			})
476		}
477
478	case "replace":
479		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
480		if wrappederr != nil {
481			*errs = append(*errs, *wrappederr)
482			return
483		}
484		f.Replace = append(f.Replace, replace)
485
486	case "retract":
487		rationale := parseDirectiveComment(block, line)
488		vi, err := parseVersionInterval(verb, "", &args, dontFixRetract)
489		if err != nil {
490			if strict {
491				wrapError(err)
492				return
493			} else {
494				// Only report errors parsing intervals in the main module. We may
495				// support additional syntax in the future, such as open and half-open
496				// intervals. Those can't be supported now, because they break the
497				// go.mod parser, even in lax mode.
498				return
499			}
500		}
501		if len(args) > 0 && strict {
502			// In the future, there may be additional information after the version.
503			errorf("unexpected token after version: %q", args[0])
504			return
505		}
506		retract := &Retract{
507			VersionInterval: vi,
508			Rationale:       rationale,
509			Syntax:          line,
510		}
511		f.Retract = append(f.Retract, retract)
512	}
513}
514
515func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) {
516	wrapModPathError := func(modPath string, err error) *Error {
517		return &Error{
518			Filename: filename,
519			Pos:      line.Start,
520			ModPath:  modPath,
521			Verb:     verb,
522			Err:      err,
523		}
524	}
525	wrapError := func(err error) *Error {
526		return &Error{
527			Filename: filename,
528			Pos:      line.Start,
529			Err:      err,
530		}
531	}
532	errorf := func(format string, args ...interface{}) *Error {
533		return wrapError(fmt.Errorf(format, args...))
534	}
535
536	arrow := 2
537	if len(args) >= 2 && args[1] == "=>" {
538		arrow = 1
539	}
540	if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
541		return nil, 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)
542	}
543	s, err := parseString(&args[0])
544	if err != nil {
545		return nil, errorf("invalid quoted string: %v", err)
546	}
547	pathMajor, err := modulePathMajor(s)
548	if err != nil {
549		return nil, wrapModPathError(s, err)
550
551	}
552	var v string
553	if arrow == 2 {
554		v, err = parseVersion(verb, s, &args[1], fix)
555		if err != nil {
556			return nil, wrapError(err)
557		}
558		if err := module.CheckPathMajor(v, pathMajor); err != nil {
559			return nil, wrapModPathError(s, err)
560		}
561	}
562	ns, err := parseString(&args[arrow+1])
563	if err != nil {
564		return nil, errorf("invalid quoted string: %v", err)
565	}
566	nv := ""
567	if len(args) == arrow+2 {
568		if !IsDirectoryPath(ns) {
569			if strings.Contains(ns, "@") {
570				return nil, errorf("replacement module must match format 'path version', not 'path@version'")
571			}
572			return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)")
573		}
574		if filepath.Separator == '/' && strings.Contains(ns, `\`) {
575			return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
576		}
577	}
578	if len(args) == arrow+3 {
579		nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
580		if err != nil {
581			return nil, wrapError(err)
582		}
583		if IsDirectoryPath(ns) {
584			return nil, errorf("replacement module directory path %q cannot have version", ns)
585		}
586	}
587	return &Replace{
588		Old:    module.Version{Path: s, Version: v},
589		New:    module.Version{Path: ns, Version: nv},
590		Syntax: line,
591	}, nil
592}
593
594// fixRetract applies fix to each retract directive in f, appending any errors
595// to errs.
596//
597// Most versions are fixed as we parse the file, but for retract directives,
598// the relevant module path is the one specified with the module directive,
599// and that might appear at the end of the file (or not at all).
600func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
601	if fix == nil {
602		return
603	}
604	path := ""
605	if f.Module != nil {
606		path = f.Module.Mod.Path
607	}
608	var r *Retract
609	wrapError := func(err error) {
610		*errs = append(*errs, Error{
611			Filename: f.Syntax.Name,
612			Pos:      r.Syntax.Start,
613			Err:      err,
614		})
615	}
616
617	for _, r = range f.Retract {
618		if path == "" {
619			wrapError(errors.New("no module directive found, so retract cannot be used"))
620			return // only print the first one of these
621		}
622
623		args := r.Syntax.Token
624		if args[0] == "retract" {
625			args = args[1:]
626		}
627		vi, err := parseVersionInterval("retract", path, &args, fix)
628		if err != nil {
629			wrapError(err)
630		}
631		r.VersionInterval = vi
632	}
633}
634
635func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
636	wrapError := func(err error) {
637		*errs = append(*errs, Error{
638			Filename: f.Syntax.Name,
639			Pos:      line.Start,
640			Err:      err,
641		})
642	}
643	errorf := func(format string, args ...interface{}) {
644		wrapError(fmt.Errorf(format, args...))
645	}
646
647	switch verb {
648	default:
649		errorf("unknown directive: %s", verb)
650
651	case "go":
652		if f.Go != nil {
653			errorf("repeated go statement")
654			return
655		}
656		if len(args) != 1 {
657			errorf("go directive expects exactly one argument")
658			return
659		} else if !GoVersionRE.MatchString(args[0]) {
660			errorf("invalid go version '%s': must match format 1.23.0", args[0])
661			return
662		}
663
664		f.Go = &Go{Syntax: line}
665		f.Go.Version = args[0]
666
667	case "toolchain":
668		if f.Toolchain != nil {
669			errorf("repeated toolchain statement")
670			return
671		}
672		if len(args) != 1 {
673			errorf("toolchain directive expects exactly one argument")
674			return
675		} else if !ToolchainRE.MatchString(args[0]) {
676			errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0])
677			return
678		}
679
680		f.Toolchain = &Toolchain{Syntax: line}
681		f.Toolchain.Name = args[0]
682
683	case "godebug":
684		if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") {
685			errorf("usage: godebug key=value")
686			return
687		}
688		key, value, ok := strings.Cut(args[0], "=")
689		if !ok {
690			errorf("usage: godebug key=value")
691			return
692		}
693		f.Godebug = append(f.Godebug, &Godebug{
694			Key:    key,
695			Value:  value,
696			Syntax: line,
697		})
698
699	case "use":
700		if len(args) != 1 {
701			errorf("usage: %s local/dir", verb)
702			return
703		}
704		s, err := parseString(&args[0])
705		if err != nil {
706			errorf("invalid quoted string: %v", err)
707			return
708		}
709		f.Use = append(f.Use, &Use{
710			Path:   s,
711			Syntax: line,
712		})
713
714	case "replace":
715		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
716		if wrappederr != nil {
717			*errs = append(*errs, *wrappederr)
718			return
719		}
720		f.Replace = append(f.Replace, replace)
721	}
722}
723
724// IsDirectoryPath reports whether the given path should be interpreted as a directory path.
725// Just like on the go command line, relative paths starting with a '.' or '..' path component
726// and rooted paths are directory paths; the rest are module paths.
727func IsDirectoryPath(ns string) bool {
728	// Because go.mod files can move from one system to another,
729	// we check all known path syntaxes, both Unix and Windows.
730	return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) ||
731		ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) ||
732		strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) ||
733		len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
734}
735
736// MustQuote reports whether s must be quoted in order to appear as
737// a single token in a go.mod line.
738func MustQuote(s string) bool {
739	for _, r := range s {
740		switch r {
741		case ' ', '"', '\'', '`':
742			return true
743
744		case '(', ')', '[', ']', '{', '}', ',':
745			if len(s) > 1 {
746				return true
747			}
748
749		default:
750			if !unicode.IsPrint(r) {
751				return true
752			}
753		}
754	}
755	return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
756}
757
758// AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
759// the quotation of s.
760func AutoQuote(s string) string {
761	if MustQuote(s) {
762		return strconv.Quote(s)
763	}
764	return s
765}
766
767func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) {
768	toks := *args
769	if len(toks) == 0 || toks[0] == "(" {
770		return VersionInterval{}, fmt.Errorf("expected '[' or version")
771	}
772	if toks[0] != "[" {
773		v, err := parseVersion(verb, path, &toks[0], fix)
774		if err != nil {
775			return VersionInterval{}, err
776		}
777		*args = toks[1:]
778		return VersionInterval{Low: v, High: v}, nil
779	}
780	toks = toks[1:]
781
782	if len(toks) == 0 {
783		return VersionInterval{}, fmt.Errorf("expected version after '['")
784	}
785	low, err := parseVersion(verb, path, &toks[0], fix)
786	if err != nil {
787		return VersionInterval{}, err
788	}
789	toks = toks[1:]
790
791	if len(toks) == 0 || toks[0] != "," {
792		return VersionInterval{}, fmt.Errorf("expected ',' after version")
793	}
794	toks = toks[1:]
795
796	if len(toks) == 0 {
797		return VersionInterval{}, fmt.Errorf("expected version after ','")
798	}
799	high, err := parseVersion(verb, path, &toks[0], fix)
800	if err != nil {
801		return VersionInterval{}, err
802	}
803	toks = toks[1:]
804
805	if len(toks) == 0 || toks[0] != "]" {
806		return VersionInterval{}, fmt.Errorf("expected ']' after version")
807	}
808	toks = toks[1:]
809
810	*args = toks
811	return VersionInterval{Low: low, High: high}, nil
812}
813
814func parseString(s *string) (string, error) {
815	t := *s
816	if strings.HasPrefix(t, `"`) {
817		var err error
818		if t, err = strconv.Unquote(t); err != nil {
819			return "", err
820		}
821	} else if strings.ContainsAny(t, "\"'`") {
822		// Other quotes are reserved both for possible future expansion
823		// and to avoid confusion. For example if someone types 'x'
824		// we want that to be a syntax error and not a literal x in literal quotation marks.
825		return "", fmt.Errorf("unquoted string cannot contain quote")
826	}
827	*s = AutoQuote(t)
828	return t, nil
829}
830
831var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`)
832
833// parseDeprecation extracts the text of comments on a "module" directive and
834// extracts a deprecation message from that.
835//
836// A deprecation message is contained in a paragraph within a block of comments
837// that starts with "Deprecated:" (case sensitive). The message runs until the
838// end of the paragraph and does not include the "Deprecated:" prefix. If the
839// comment block has multiple paragraphs that start with "Deprecated:",
840// parseDeprecation returns the message from the first.
841func parseDeprecation(block *LineBlock, line *Line) string {
842	text := parseDirectiveComment(block, line)
843	m := deprecatedRE.FindStringSubmatch(text)
844	if m == nil {
845		return ""
846	}
847	return m[1]
848}
849
850// parseDirectiveComment extracts the text of comments on a directive.
851// If the directive's line does not have comments and is part of a block that
852// does have comments, the block's comments are used.
853func parseDirectiveComment(block *LineBlock, line *Line) string {
854	comments := line.Comment()
855	if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
856		comments = block.Comment()
857	}
858	groups := [][]Comment{comments.Before, comments.Suffix}
859	var lines []string
860	for _, g := range groups {
861		for _, c := range g {
862			if !strings.HasPrefix(c.Token, "//") {
863				continue // blank line
864			}
865			lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
866		}
867	}
868	return strings.Join(lines, "\n")
869}
870
871type ErrorList []Error
872
873func (e ErrorList) Error() string {
874	errStrs := make([]string, len(e))
875	for i, err := range e {
876		errStrs[i] = err.Error()
877	}
878	return strings.Join(errStrs, "\n")
879}
880
881type Error struct {
882	Filename string
883	Pos      Position
884	Verb     string
885	ModPath  string
886	Err      error
887}
888
889func (e *Error) Error() string {
890	var pos string
891	if e.Pos.LineRune > 1 {
892		// Don't print LineRune if it's 1 (beginning of line).
893		// It's always 1 except in scanner errors, which are rare.
894		pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune)
895	} else if e.Pos.Line > 0 {
896		pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line)
897	} else if e.Filename != "" {
898		pos = fmt.Sprintf("%s: ", e.Filename)
899	}
900
901	var directive string
902	if e.ModPath != "" {
903		directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
904	} else if e.Verb != "" {
905		directive = fmt.Sprintf("%s: ", e.Verb)
906	}
907
908	return pos + directive + e.Err.Error()
909}
910
911func (e *Error) Unwrap() error { return e.Err }
912
913func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
914	t, err := parseString(s)
915	if err != nil {
916		return "", &Error{
917			Verb:    verb,
918			ModPath: path,
919			Err: &module.InvalidVersionError{
920				Version: *s,
921				Err:     err,
922			},
923		}
924	}
925	if fix != nil {
926		fixed, err := fix(path, t)
927		if err != nil {
928			if err, ok := err.(*module.ModuleError); ok {
929				return "", &Error{
930					Verb:    verb,
931					ModPath: path,
932					Err:     err.Err,
933				}
934			}
935			return "", err
936		}
937		t = fixed
938	} else {
939		cv := module.CanonicalVersion(t)
940		if cv == "" {
941			return "", &Error{
942				Verb:    verb,
943				ModPath: path,
944				Err: &module.InvalidVersionError{
945					Version: t,
946					Err:     errors.New("must be of the form v1.2.3"),
947				},
948			}
949		}
950		t = cv
951	}
952	*s = t
953	return *s, nil
954}
955
956func modulePathMajor(path string) (string, error) {
957	_, major, ok := module.SplitPathVersion(path)
958	if !ok {
959		return "", fmt.Errorf("invalid module path")
960	}
961	return major, nil
962}
963
964func (f *File) Format() ([]byte, error) {
965	return Format(f.Syntax), nil
966}
967
968// Cleanup cleans up the file f after any edit operations.
969// To avoid quadratic behavior, modifications like [File.DropRequire]
970// clear the entry but do not remove it from the slice.
971// Cleanup cleans out all the cleared entries.
972func (f *File) Cleanup() {
973	w := 0
974	for _, g := range f.Godebug {
975		if g.Key != "" {
976			f.Godebug[w] = g
977			w++
978		}
979	}
980	f.Godebug = f.Godebug[:w]
981
982	w = 0
983	for _, r := range f.Require {
984		if r.Mod.Path != "" {
985			f.Require[w] = r
986			w++
987		}
988	}
989	f.Require = f.Require[:w]
990
991	w = 0
992	for _, x := range f.Exclude {
993		if x.Mod.Path != "" {
994			f.Exclude[w] = x
995			w++
996		}
997	}
998	f.Exclude = f.Exclude[:w]
999
1000	w = 0
1001	for _, r := range f.Replace {
1002		if r.Old.Path != "" {
1003			f.Replace[w] = r
1004			w++
1005		}
1006	}
1007	f.Replace = f.Replace[:w]
1008
1009	w = 0
1010	for _, r := range f.Retract {
1011		if r.Low != "" || r.High != "" {
1012			f.Retract[w] = r
1013			w++
1014		}
1015	}
1016	f.Retract = f.Retract[:w]
1017
1018	f.Syntax.Cleanup()
1019}
1020
1021func (f *File) AddGoStmt(version string) error {
1022	if !GoVersionRE.MatchString(version) {
1023		return fmt.Errorf("invalid language version %q", version)
1024	}
1025	if f.Go == nil {
1026		var hint Expr
1027		if f.Module != nil && f.Module.Syntax != nil {
1028			hint = f.Module.Syntax
1029		} else if f.Syntax == nil {
1030			f.Syntax = new(FileSyntax)
1031		}
1032		f.Go = &Go{
1033			Version: version,
1034			Syntax:  f.Syntax.addLine(hint, "go", version),
1035		}
1036	} else {
1037		f.Go.Version = version
1038		f.Syntax.updateLine(f.Go.Syntax, "go", version)
1039	}
1040	return nil
1041}
1042
1043// DropGoStmt deletes the go statement from the file.
1044func (f *File) DropGoStmt() {
1045	if f.Go != nil {
1046		f.Go.Syntax.markRemoved()
1047		f.Go = nil
1048	}
1049}
1050
1051// DropToolchainStmt deletes the toolchain statement from the file.
1052func (f *File) DropToolchainStmt() {
1053	if f.Toolchain != nil {
1054		f.Toolchain.Syntax.markRemoved()
1055		f.Toolchain = nil
1056	}
1057}
1058
1059func (f *File) AddToolchainStmt(name string) error {
1060	if !ToolchainRE.MatchString(name) {
1061		return fmt.Errorf("invalid toolchain name %q", name)
1062	}
1063	if f.Toolchain == nil {
1064		var hint Expr
1065		if f.Go != nil && f.Go.Syntax != nil {
1066			hint = f.Go.Syntax
1067		} else if f.Module != nil && f.Module.Syntax != nil {
1068			hint = f.Module.Syntax
1069		}
1070		f.Toolchain = &Toolchain{
1071			Name:   name,
1072			Syntax: f.Syntax.addLine(hint, "toolchain", name),
1073		}
1074	} else {
1075		f.Toolchain.Name = name
1076		f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
1077	}
1078	return nil
1079}
1080
1081// AddGodebug sets the first godebug line for key to value,
1082// preserving any existing comments for that line and removing all
1083// other godebug lines for key.
1084//
1085// If no line currently exists for key, AddGodebug adds a new line
1086// at the end of the last godebug block.
1087func (f *File) AddGodebug(key, value string) error {
1088	need := true
1089	for _, g := range f.Godebug {
1090		if g.Key == key {
1091			if need {
1092				g.Value = value
1093				f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value)
1094				need = false
1095			} else {
1096				g.Syntax.markRemoved()
1097				*g = Godebug{}
1098			}
1099		}
1100	}
1101
1102	if need {
1103		f.addNewGodebug(key, value)
1104	}
1105	return nil
1106}
1107
1108// addNewGodebug adds a new godebug key=value line at the end
1109// of the last godebug block, regardless of any existing godebug lines for key.
1110func (f *File) addNewGodebug(key, value string) {
1111	line := f.Syntax.addLine(nil, "godebug", key+"="+value)
1112	g := &Godebug{
1113		Key:    key,
1114		Value:  value,
1115		Syntax: line,
1116	}
1117	f.Godebug = append(f.Godebug, g)
1118}
1119
1120// AddRequire sets the first require line for path to version vers,
1121// preserving any existing comments for that line and removing all
1122// other lines for path.
1123//
1124// If no line currently exists for path, AddRequire adds a new line
1125// at the end of the last require block.
1126func (f *File) AddRequire(path, vers string) error {
1127	need := true
1128	for _, r := range f.Require {
1129		if r.Mod.Path == path {
1130			if need {
1131				r.Mod.Version = vers
1132				f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
1133				need = false
1134			} else {
1135				r.Syntax.markRemoved()
1136				*r = Require{}
1137			}
1138		}
1139	}
1140
1141	if need {
1142		f.AddNewRequire(path, vers, false)
1143	}
1144	return nil
1145}
1146
1147// AddNewRequire adds a new require line for path at version vers at the end of
1148// the last require block, regardless of any existing require lines for path.
1149func (f *File) AddNewRequire(path, vers string, indirect bool) {
1150	line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
1151	r := &Require{
1152		Mod:    module.Version{Path: path, Version: vers},
1153		Syntax: line,
1154	}
1155	r.setIndirect(indirect)
1156	f.Require = append(f.Require, r)
1157}
1158
1159// SetRequire updates the requirements of f to contain exactly req, preserving
1160// the existing block structure and line comment contents (except for 'indirect'
1161// markings) for the first requirement on each named module path.
1162//
1163// The Syntax field is ignored for the requirements in req.
1164//
1165// Any requirements not already present in the file are added to the block
1166// containing the last require line.
1167//
1168// The requirements in req must specify at most one distinct version for each
1169// module path.
1170//
1171// If any existing requirements may be removed, the caller should call
1172// [File.Cleanup] after all edits are complete.
1173func (f *File) SetRequire(req []*Require) {
1174	type elem struct {
1175		version  string
1176		indirect bool
1177	}
1178	need := make(map[string]elem)
1179	for _, r := range req {
1180		if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version {
1181			panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version))
1182		}
1183		need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect}
1184	}
1185
1186	// Update or delete the existing Require entries to preserve
1187	// only the first for each module path in req.
1188	for _, r := range f.Require {
1189		e, ok := need[r.Mod.Path]
1190		if ok {
1191			r.setVersion(e.version)
1192			r.setIndirect(e.indirect)
1193		} else {
1194			r.markRemoved()
1195		}
1196		delete(need, r.Mod.Path)
1197	}
1198
1199	// Add new entries in the last block of the file for any paths that weren't
1200	// already present.
1201	//
1202	// This step is nondeterministic, but the final result will be deterministic
1203	// because we will sort the block.
1204	for path, e := range need {
1205		f.AddNewRequire(path, e.version, e.indirect)
1206	}
1207
1208	f.SortBlocks()
1209}
1210
1211// SetRequireSeparateIndirect updates the requirements of f to contain the given
1212// requirements. Comment contents (except for 'indirect' markings) are retained
1213// from the first existing requirement for each module path. Like SetRequire,
1214// SetRequireSeparateIndirect adds requirements for new paths in req,
1215// updates the version and "// indirect" comment on existing requirements,
1216// and deletes requirements on paths not in req. Existing duplicate requirements
1217// are deleted.
1218//
1219// As its name suggests, SetRequireSeparateIndirect puts direct and indirect
1220// requirements into two separate blocks, one containing only direct
1221// requirements, and the other containing only indirect requirements.
1222// SetRequireSeparateIndirect may move requirements between these two blocks
1223// when their indirect markings change. However, SetRequireSeparateIndirect
1224// won't move requirements from other blocks, especially blocks with comments.
1225//
1226// If the file initially has one uncommented block of requirements,
1227// SetRequireSeparateIndirect will split it into a direct-only and indirect-only
1228// block. This aids in the transition to separate blocks.
1229func (f *File) SetRequireSeparateIndirect(req []*Require) {
1230	// hasComments returns whether a line or block has comments
1231	// other than "indirect".
1232	hasComments := func(c Comments) bool {
1233		return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 ||
1234			(len(c.Suffix) == 1 &&
1235				strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect")
1236	}
1237
1238	// moveReq adds r to block. If r was in another block, moveReq deletes
1239	// it from that block and transfers its comments.
1240	moveReq := func(r *Require, block *LineBlock) {
1241		var line *Line
1242		if r.Syntax == nil {
1243			line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}}
1244			r.Syntax = line
1245			if r.Indirect {
1246				r.setIndirect(true)
1247			}
1248		} else {
1249			line = new(Line)
1250			*line = *r.Syntax
1251			if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" {
1252				line.Token = line.Token[1:]
1253			}
1254			r.Syntax.Token = nil // Cleanup will delete the old line.
1255			r.Syntax = line
1256		}
1257		line.InBlock = true
1258		block.Line = append(block.Line, line)
1259	}
1260
1261	// Examine existing require lines and blocks.
1262	var (
1263		// We may insert new requirements into the last uncommented
1264		// direct-only and indirect-only blocks. We may also move requirements
1265		// to the opposite block if their indirect markings change.
1266		lastDirectIndex   = -1
1267		lastIndirectIndex = -1
1268
1269		// If there are no direct-only or indirect-only blocks, a new block may
1270		// be inserted after the last require line or block.
1271		lastRequireIndex = -1
1272
1273		// If there's only one require line or block, and it's uncommented,
1274		// we'll move its requirements to the direct-only or indirect-only blocks.
1275		requireLineOrBlockCount = 0
1276
1277		// Track the block each requirement belongs to (if any) so we can
1278		// move them later.
1279		lineToBlock = make(map[*Line]*LineBlock)
1280	)
1281	for i, stmt := range f.Syntax.Stmt {
1282		switch stmt := stmt.(type) {
1283		case *Line:
1284			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
1285				continue
1286			}
1287			lastRequireIndex = i
1288			requireLineOrBlockCount++
1289			if !hasComments(stmt.Comments) {
1290				if isIndirect(stmt) {
1291					lastIndirectIndex = i
1292				} else {
1293					lastDirectIndex = i
1294				}
1295			}
1296
1297		case *LineBlock:
1298			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
1299				continue
1300			}
1301			lastRequireIndex = i
1302			requireLineOrBlockCount++
1303			allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
1304			allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
1305			for _, line := range stmt.Line {
1306				lineToBlock[line] = stmt
1307				if hasComments(line.Comments) {
1308					allDirect = false
1309					allIndirect = false
1310				} else if isIndirect(line) {
1311					allDirect = false
1312				} else {
1313					allIndirect = false
1314				}
1315			}
1316			if allDirect {
1317				lastDirectIndex = i
1318			}
1319			if allIndirect {
1320				lastIndirectIndex = i
1321			}
1322		}
1323	}
1324
1325	oneFlatUncommentedBlock := requireLineOrBlockCount == 1 &&
1326		!hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment())
1327
1328	// Create direct and indirect blocks if needed. Convert lines into blocks
1329	// if needed. If we end up with an empty block or a one-line block,
1330	// Cleanup will delete it or convert it to a line later.
1331	insertBlock := func(i int) *LineBlock {
1332		block := &LineBlock{Token: []string{"require"}}
1333		f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
1334		copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
1335		f.Syntax.Stmt[i] = block
1336		return block
1337	}
1338
1339	ensureBlock := func(i int) *LineBlock {
1340		switch stmt := f.Syntax.Stmt[i].(type) {
1341		case *LineBlock:
1342			return stmt
1343		case *Line:
1344			block := &LineBlock{
1345				Token: []string{"require"},
1346				Line:  []*Line{stmt},
1347			}
1348			stmt.Token = stmt.Token[1:] // remove "require"
1349			stmt.InBlock = true
1350			f.Syntax.Stmt[i] = block
1351			return block
1352		default:
1353			panic(fmt.Sprintf("unexpected statement: %v", stmt))
1354		}
1355	}
1356
1357	var lastDirectBlock *LineBlock
1358	if lastDirectIndex < 0 {
1359		if lastIndirectIndex >= 0 {
1360			lastDirectIndex = lastIndirectIndex
1361			lastIndirectIndex++
1362		} else if lastRequireIndex >= 0 {
1363			lastDirectIndex = lastRequireIndex + 1
1364		} else {
1365			lastDirectIndex = len(f.Syntax.Stmt)
1366		}
1367		lastDirectBlock = insertBlock(lastDirectIndex)
1368	} else {
1369		lastDirectBlock = ensureBlock(lastDirectIndex)
1370	}
1371
1372	var lastIndirectBlock *LineBlock
1373	if lastIndirectIndex < 0 {
1374		lastIndirectIndex = lastDirectIndex + 1
1375		lastIndirectBlock = insertBlock(lastIndirectIndex)
1376	} else {
1377		lastIndirectBlock = ensureBlock(lastIndirectIndex)
1378	}
1379
1380	// Delete requirements we don't want anymore.
1381	// Update versions and indirect comments on requirements we want to keep.
1382	// If a requirement is in last{Direct,Indirect}Block with the wrong
1383	// indirect marking after this, or if the requirement is in an single
1384	// uncommented mixed block (oneFlatUncommentedBlock), move it to the
1385	// correct block.
1386	//
1387	// Some blocks may be empty after this. Cleanup will remove them.
1388	need := make(map[string]*Require)
1389	for _, r := range req {
1390		need[r.Mod.Path] = r
1391	}
1392	have := make(map[string]*Require)
1393	for _, r := range f.Require {
1394		path := r.Mod.Path
1395		if need[path] == nil || have[path] != nil {
1396			// Requirement not needed, or duplicate requirement. Delete.
1397			r.markRemoved()
1398			continue
1399		}
1400		have[r.Mod.Path] = r
1401		r.setVersion(need[path].Mod.Version)
1402		r.setIndirect(need[path].Indirect)
1403		if need[path].Indirect &&
1404			(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
1405			moveReq(r, lastIndirectBlock)
1406		} else if !need[path].Indirect &&
1407			(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
1408			moveReq(r, lastDirectBlock)
1409		}
1410	}
1411
1412	// Add new requirements.
1413	for path, r := range need {
1414		if have[path] == nil {
1415			if r.Indirect {
1416				moveReq(r, lastIndirectBlock)
1417			} else {
1418				moveReq(r, lastDirectBlock)
1419			}
1420			f.Require = append(f.Require, r)
1421		}
1422	}
1423
1424	f.SortBlocks()
1425}
1426
1427func (f *File) DropGodebug(key string) error {
1428	for _, g := range f.Godebug {
1429		if g.Key == key {
1430			g.Syntax.markRemoved()
1431			*g = Godebug{}
1432		}
1433	}
1434	return nil
1435}
1436
1437func (f *File) DropRequire(path string) error {
1438	for _, r := range f.Require {
1439		if r.Mod.Path == path {
1440			r.Syntax.markRemoved()
1441			*r = Require{}
1442		}
1443	}
1444	return nil
1445}
1446
1447// AddExclude adds a exclude statement to the mod file. Errors if the provided
1448// version is not a canonical version string
1449func (f *File) AddExclude(path, vers string) error {
1450	if err := checkCanonicalVersion(path, vers); err != nil {
1451		return err
1452	}
1453
1454	var hint *Line
1455	for _, x := range f.Exclude {
1456		if x.Mod.Path == path && x.Mod.Version == vers {
1457			return nil
1458		}
1459		if x.Mod.Path == path {
1460			hint = x.Syntax
1461		}
1462	}
1463
1464	f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
1465	return nil
1466}
1467
1468func (f *File) DropExclude(path, vers string) error {
1469	for _, x := range f.Exclude {
1470		if x.Mod.Path == path && x.Mod.Version == vers {
1471			x.Syntax.markRemoved()
1472			*x = Exclude{}
1473		}
1474	}
1475	return nil
1476}
1477
1478func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
1479	return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
1480}
1481
1482func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
1483	need := true
1484	old := module.Version{Path: oldPath, Version: oldVers}
1485	new := module.Version{Path: newPath, Version: newVers}
1486	tokens := []string{"replace", AutoQuote(oldPath)}
1487	if oldVers != "" {
1488		tokens = append(tokens, oldVers)
1489	}
1490	tokens = append(tokens, "=>", AutoQuote(newPath))
1491	if newVers != "" {
1492		tokens = append(tokens, newVers)
1493	}
1494
1495	var hint *Line
1496	for _, r := range *replace {
1497		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
1498			if need {
1499				// Found replacement for old; update to use new.
1500				r.New = new
1501				syntax.updateLine(r.Syntax, tokens...)
1502				need = false
1503				continue
1504			}
1505			// Already added; delete other replacements for same.
1506			r.Syntax.markRemoved()
1507			*r = Replace{}
1508		}
1509		if r.Old.Path == oldPath {
1510			hint = r.Syntax
1511		}
1512	}
1513	if need {
1514		*replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
1515	}
1516	return nil
1517}
1518
1519func (f *File) DropReplace(oldPath, oldVers string) error {
1520	for _, r := range f.Replace {
1521		if r.Old.Path == oldPath && r.Old.Version == oldVers {
1522			r.Syntax.markRemoved()
1523			*r = Replace{}
1524		}
1525	}
1526	return nil
1527}
1528
1529// AddRetract adds a retract statement to the mod file. Errors if the provided
1530// version interval does not consist of canonical version strings
1531func (f *File) AddRetract(vi VersionInterval, rationale string) error {
1532	var path string
1533	if f.Module != nil {
1534		path = f.Module.Mod.Path
1535	}
1536	if err := checkCanonicalVersion(path, vi.High); err != nil {
1537		return err
1538	}
1539	if err := checkCanonicalVersion(path, vi.Low); err != nil {
1540		return err
1541	}
1542
1543	r := &Retract{
1544		VersionInterval: vi,
1545	}
1546	if vi.Low == vi.High {
1547		r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
1548	} else {
1549		r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
1550	}
1551	if rationale != "" {
1552		for _, line := range strings.Split(rationale, "\n") {
1553			com := Comment{Token: "// " + line}
1554			r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
1555		}
1556	}
1557	return nil
1558}
1559
1560func (f *File) DropRetract(vi VersionInterval) error {
1561	for _, r := range f.Retract {
1562		if r.VersionInterval == vi {
1563			r.Syntax.markRemoved()
1564			*r = Retract{}
1565		}
1566	}
1567	return nil
1568}
1569
1570func (f *File) SortBlocks() {
1571	f.removeDups() // otherwise sorting is unsafe
1572
1573	// semanticSortForExcludeVersionV is the Go version (plus leading "v") at which
1574	// lines in exclude blocks start to use semantic sort instead of lexicographic sort.
1575	// See go.dev/issue/60028.
1576	const semanticSortForExcludeVersionV = "v1.21"
1577	useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0
1578
1579	for _, stmt := range f.Syntax.Stmt {
1580		block, ok := stmt.(*LineBlock)
1581		if !ok {
1582			continue
1583		}
1584		less := lineLess
1585		if block.Token[0] == "exclude" && useSemanticSortForExclude {
1586			less = lineExcludeLess
1587		} else if block.Token[0] == "retract" {
1588			less = lineRetractLess
1589		}
1590		sort.SliceStable(block.Line, func(i, j int) bool {
1591			return less(block.Line[i], block.Line[j])
1592		})
1593	}
1594}
1595
1596// removeDups removes duplicate exclude and replace directives.
1597//
1598// Earlier exclude directives take priority.
1599//
1600// Later replace directives take priority.
1601//
1602// require directives are not de-duplicated. That's left up to higher-level
1603// logic (MVS).
1604//
1605// retract directives are not de-duplicated since comments are
1606// meaningful, and versions may be retracted multiple times.
1607func (f *File) removeDups() {
1608	removeDups(f.Syntax, &f.Exclude, &f.Replace)
1609}
1610
1611func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
1612	kill := make(map[*Line]bool)
1613
1614	// Remove duplicate excludes.
1615	if exclude != nil {
1616		haveExclude := make(map[module.Version]bool)
1617		for _, x := range *exclude {
1618			if haveExclude[x.Mod] {
1619				kill[x.Syntax] = true
1620				continue
1621			}
1622			haveExclude[x.Mod] = true
1623		}
1624		var excl []*Exclude
1625		for _, x := range *exclude {
1626			if !kill[x.Syntax] {
1627				excl = append(excl, x)
1628			}
1629		}
1630		*exclude = excl
1631	}
1632
1633	// Remove duplicate replacements.
1634	// Later replacements take priority over earlier ones.
1635	haveReplace := make(map[module.Version]bool)
1636	for i := len(*replace) - 1; i >= 0; i-- {
1637		x := (*replace)[i]
1638		if haveReplace[x.Old] {
1639			kill[x.Syntax] = true
1640			continue
1641		}
1642		haveReplace[x.Old] = true
1643	}
1644	var repl []*Replace
1645	for _, x := range *replace {
1646		if !kill[x.Syntax] {
1647			repl = append(repl, x)
1648		}
1649	}
1650	*replace = repl
1651
1652	// Duplicate require and retract directives are not removed.
1653
1654	// Drop killed statements from the syntax tree.
1655	var stmts []Expr
1656	for _, stmt := range syntax.Stmt {
1657		switch stmt := stmt.(type) {
1658		case *Line:
1659			if kill[stmt] {
1660				continue
1661			}
1662		case *LineBlock:
1663			var lines []*Line
1664			for _, line := range stmt.Line {
1665				if !kill[line] {
1666					lines = append(lines, line)
1667				}
1668			}
1669			stmt.Line = lines
1670			if len(lines) == 0 {
1671				continue
1672			}
1673		}
1674		stmts = append(stmts, stmt)
1675	}
1676	syntax.Stmt = stmts
1677}
1678
1679// lineLess returns whether li should be sorted before lj. It sorts
1680// lexicographically without assigning any special meaning to tokens.
1681func lineLess(li, lj *Line) bool {
1682	for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
1683		if li.Token[k] != lj.Token[k] {
1684			return li.Token[k] < lj.Token[k]
1685		}
1686	}
1687	return len(li.Token) < len(lj.Token)
1688}
1689
1690// lineExcludeLess reports whether li should be sorted before lj for lines in
1691// an "exclude" block.
1692func lineExcludeLess(li, lj *Line) bool {
1693	if len(li.Token) != 2 || len(lj.Token) != 2 {
1694		// Not a known exclude specification.
1695		// Fall back to sorting lexicographically.
1696		return lineLess(li, lj)
1697	}
1698	// An exclude specification has two tokens: ModulePath and Version.
1699	// Compare module path by string order and version by semver rules.
1700	if pi, pj := li.Token[0], lj.Token[0]; pi != pj {
1701		return pi < pj
1702	}
1703	return semver.Compare(li.Token[1], lj.Token[1]) < 0
1704}
1705
1706// lineRetractLess returns whether li should be sorted before lj for lines in
1707// a "retract" block. It treats each line as a version interval. Single versions
1708// are compared as if they were intervals with the same low and high version.
1709// Intervals are sorted in descending order, first by low version, then by
1710// high version, using semver.Compare.
1711func lineRetractLess(li, lj *Line) bool {
1712	interval := func(l *Line) VersionInterval {
1713		if len(l.Token) == 1 {
1714			return VersionInterval{Low: l.Token[0], High: l.Token[0]}
1715		} else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
1716			return VersionInterval{Low: l.Token[1], High: l.Token[3]}
1717		} else {
1718			// Line in unknown format. Treat as an invalid version.
1719			return VersionInterval{}
1720		}
1721	}
1722	vii := interval(li)
1723	vij := interval(lj)
1724	if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
1725		return cmp > 0
1726	}
1727	return semver.Compare(vii.High, vij.High) > 0
1728}
1729
1730// checkCanonicalVersion returns a non-nil error if vers is not a canonical
1731// version string or does not match the major version of path.
1732//
1733// If path is non-empty, the error text suggests a format with a major version
1734// corresponding to the path.
1735func checkCanonicalVersion(path, vers string) error {
1736	_, pathMajor, pathMajorOk := module.SplitPathVersion(path)
1737
1738	if vers == "" || vers != module.CanonicalVersion(vers) {
1739		if pathMajor == "" {
1740			return &module.InvalidVersionError{
1741				Version: vers,
1742				Err:     fmt.Errorf("must be of the form v1.2.3"),
1743			}
1744		}
1745		return &module.InvalidVersionError{
1746			Version: vers,
1747			Err:     fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)),
1748		}
1749	}
1750
1751	if pathMajorOk {
1752		if err := module.CheckPathMajor(vers, pathMajor); err != nil {
1753			if pathMajor == "" {
1754				// In this context, the user probably wrote "v2.3.4" when they meant
1755				// "v2.3.4+incompatible". Suggest that instead of "v0 or v1".
1756				return &module.InvalidVersionError{
1757					Version: vers,
1758					Err:     fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)),
1759				}
1760			}
1761			return err
1762		}
1763	}
1764
1765	return nil
1766}
1767