package modfile
import (
+ "cmp"
"errors"
"fmt"
"path/filepath"
- "sort"
+ "slices"
"strconv"
"strings"
"unicode"
Replace []*Replace
Retract []*Retract
Tool []*Tool
+ Ignore []*Ignore
Syntax *FileSyntax
}
Syntax *Line
}
+// An Ignore is a single ignore statement.
+type Ignore struct {
+ Path string
+ Syntax *Line
+}
+
// A VersionInterval represents a range of versions with upper and lower bounds.
// Intervals are closed: both bounds are included. When Low is equal to High,
// the interval may refer to a single version ('v1.2.3') or an interval
})
}
continue
- case "module", "godebug", "require", "exclude", "replace", "retract", "tool":
+ case "module", "godebug", "require", "exclude", "replace", "retract", "tool", "ignore":
for _, l := range x.Line {
f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
}
// and simply ignore those statements.
if !strict {
switch verb {
- case "go", "module", "retract", "require":
+ case "go", "module", "retract", "require", "ignore":
// want these even for dependency go.mods
default:
return
Path: s,
Syntax: line,
})
+
+ case "ignore":
+ if len(args) != 1 {
+ errorf("ignore directive expects exactly one argument")
+ return
+ }
+ s, err := parseString(&args[0])
+ if err != nil {
+ errorf("invalid quoted string: %v", err)
+ return
+ }
+ f.Ignore = append(f.Ignore, &Ignore{
+ Path: s,
+ Syntax: line,
+ })
}
}
return nil
}
+// AddIgnore adds a new ignore directive with the given path.
+// It does nothing if the ignore line already exists.
+func (f *File) AddIgnore(path string) error {
+ for _, t := range f.Ignore {
+ if t.Path == path {
+ return nil
+ }
+ }
+
+ f.Ignore = append(f.Ignore, &Ignore{
+ Path: path,
+ Syntax: f.Syntax.addLine(nil, "ignore", path),
+ })
+
+ f.SortBlocks()
+ return nil
+}
+
+// DropIgnore removes a ignore directive with the given path.
+// It does nothing if no such ignore directive exists.
+func (f *File) DropIgnore(path string) error {
+ for _, t := range f.Ignore {
+ if t.Path == path {
+ t.Syntax.markRemoved()
+ *t = Ignore{}
+ }
+ }
+ return nil
+}
+
func (f *File) SortBlocks() {
f.removeDups() // otherwise sorting is unsafe
if !ok {
continue
}
- less := lineLess
+ less := compareLine
if block.Token[0] == "exclude" && useSemanticSortForExclude {
- less = lineExcludeLess
+ less = compareLineExclude
} else if block.Token[0] == "retract" {
- less = lineRetractLess
+ less = compareLineRetract
}
- sort.SliceStable(block.Line, func(i, j int) bool {
- return less(block.Line[i], block.Line[j])
- })
+ slices.SortStableFunc(block.Line, less)
}
}
// retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times.
func (f *File) removeDups() {
- removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool)
+ removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool, &f.Ignore)
}
-func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool) {
+func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool, ignore *[]*Ignore) {
kill := make(map[*Line]bool)
// Remove duplicate excludes.
*tool = newTool
}
+ if ignore != nil {
+ haveIgnore := make(map[string]bool)
+ for _, i := range *ignore {
+ if haveIgnore[i.Path] {
+ kill[i.Syntax] = true
+ continue
+ }
+ haveIgnore[i.Path] = true
+ }
+ var newIgnore []*Ignore
+ for _, i := range *ignore {
+ if !kill[i.Syntax] {
+ newIgnore = append(newIgnore, i)
+ }
+ }
+ *ignore = newIgnore
+ }
+
// Duplicate require and retract directives are not removed.
// Drop killed statements from the syntax tree.
syntax.Stmt = stmts
}
-// lineLess returns whether li should be sorted before lj. It sorts
-// lexicographically without assigning any special meaning to tokens.
-func lineLess(li, lj *Line) bool {
+// compareLine compares li and lj. It sorts lexicographically without assigning
+// any special meaning to tokens.
+func compareLine(li, lj *Line) int {
for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
if li.Token[k] != lj.Token[k] {
- return li.Token[k] < lj.Token[k]
+ return cmp.Compare(li.Token[k], lj.Token[k])
}
}
- return len(li.Token) < len(lj.Token)
+ return cmp.Compare(len(li.Token), len(lj.Token))
}
-// lineExcludeLess reports whether li should be sorted before lj for lines in
-// an "exclude" block.
-func lineExcludeLess(li, lj *Line) bool {
+// compareLineExclude compares li and lj for lines in an "exclude" block.
+func compareLineExclude(li, lj *Line) int {
if len(li.Token) != 2 || len(lj.Token) != 2 {
// Not a known exclude specification.
// Fall back to sorting lexicographically.
- return lineLess(li, lj)
+ return compareLine(li, lj)
}
// An exclude specification has two tokens: ModulePath and Version.
// Compare module path by string order and version by semver rules.
if pi, pj := li.Token[0], lj.Token[0]; pi != pj {
- return pi < pj
+ return cmp.Compare(pi, pj)
}
- return semver.Compare(li.Token[1], lj.Token[1]) < 0
+ return semver.Compare(li.Token[1], lj.Token[1])
}
-// lineRetractLess returns whether li should be sorted before lj for lines in
-// a "retract" block. It treats each line as a version interval. Single versions
-// are compared as if they were intervals with the same low and high version.
+// compareLineRetract compares li and lj for lines in a "retract" block.
+// It treats each line as a version interval. Single versions are compared as
+// if they were intervals with the same low and high version.
// Intervals are sorted in descending order, first by low version, then by
-// high version, using semver.Compare.
-func lineRetractLess(li, lj *Line) bool {
+// high version, using [semver.Compare].
+func compareLineRetract(li, lj *Line) int {
interval := func(l *Line) VersionInterval {
if len(l.Token) == 1 {
return VersionInterval{Low: l.Token[0], High: l.Token[0]}
vii := interval(li)
vij := interval(lj)
if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
- return cmp > 0
+ return -cmp
}
- return semver.Compare(vii.High, vij.High) > 0
+ return -semver.Compare(vii.High, vij.High)
}
// checkCanonicalVersion returns a non-nil error if vers is not a canonical