1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663 |
- // Copyright 2018 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // Package modfile implements a parser and formatter for go.mod files.
- //
- // The go.mod syntax is described in
- // https://golang.org/cmd/go/#hdr-The_go_mod_file.
- //
- // The Parse and ParseLax functions both parse a go.mod file and return an
- // abstract syntax tree. ParseLax ignores unknown statements and may be used to
- // parse go.mod files that may have been developed with newer versions of Go.
- //
- // The File struct returned by Parse and ParseLax represent an abstract
- // go.mod file. File has several methods like AddNewRequire and DropReplace
- // that can be used to programmatically edit a file.
- //
- // The Format function formats a File back to a byte slice which can be
- // written to a file.
- package modfile
- import (
- "errors"
- "fmt"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "unicode"
- "golang.org/x/mod/internal/lazyregexp"
- "golang.org/x/mod/module"
- "golang.org/x/mod/semver"
- )
- // A File is the parsed, interpreted form of a go.mod file.
- type File struct {
- Module *Module
- Go *Go
- Toolchain *Toolchain
- Require []*Require
- Exclude []*Exclude
- Replace []*Replace
- Retract []*Retract
- Syntax *FileSyntax
- }
- // A Module is the module statement.
- type Module struct {
- Mod module.Version
- Deprecated string
- Syntax *Line
- }
- // A Go is the go statement.
- type Go struct {
- Version string // "1.23"
- Syntax *Line
- }
- // A Toolchain is the toolchain statement.
- type Toolchain struct {
- Name string // "go1.21rc1"
- Syntax *Line
- }
- // An Exclude is a single exclude statement.
- type Exclude struct {
- Mod module.Version
- Syntax *Line
- }
- // A Replace is a single replace statement.
- type Replace struct {
- Old module.Version
- New module.Version
- Syntax *Line
- }
- // A Retract is a single retract statement.
- type Retract struct {
- VersionInterval
- Rationale 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
- // ('[v1.2.3, v1.2.3]'); both have the same representation.
- type VersionInterval struct {
- Low, High string
- }
- // A Require is a single require statement.
- type Require struct {
- Mod module.Version
- Indirect bool // has "// indirect" comment
- Syntax *Line
- }
- func (r *Require) markRemoved() {
- r.Syntax.markRemoved()
- *r = Require{}
- }
- func (r *Require) setVersion(v string) {
- r.Mod.Version = v
- if line := r.Syntax; len(line.Token) > 0 {
- if line.InBlock {
- // If the line is preceded by an empty line, remove it; see
- // https://golang.org/issue/33779.
- if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 {
- line.Comments.Before = line.Comments.Before[:0]
- }
- if len(line.Token) >= 2 { // example.com v1.2.3
- line.Token[1] = v
- }
- } else {
- if len(line.Token) >= 3 { // require example.com v1.2.3
- line.Token[2] = v
- }
- }
- }
- }
- // setIndirect sets line to have (or not have) a "// indirect" comment.
- func (r *Require) setIndirect(indirect bool) {
- r.Indirect = indirect
- line := r.Syntax
- if isIndirect(line) == indirect {
- return
- }
- if indirect {
- // Adding comment.
- if len(line.Suffix) == 0 {
- // New comment.
- line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
- return
- }
- com := &line.Suffix[0]
- text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash)))
- if text == "" {
- // Empty comment.
- com.Token = "// indirect"
- return
- }
- // Insert at beginning of existing comment.
- com.Token = "// indirect; " + text
- return
- }
- // Removing comment.
- f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
- if f == "indirect" {
- // Remove whole comment.
- line.Suffix = nil
- return
- }
- // Remove comment prefix.
- com := &line.Suffix[0]
- i := strings.Index(com.Token, "indirect;")
- com.Token = "//" + com.Token[i+len("indirect;"):]
- }
- // isIndirect reports whether line has a "// indirect" comment,
- // meaning it is in go.mod only for its effect on indirect dependencies,
- // so that it can be dropped entirely once the effective version of the
- // indirect dependency reaches the given minimum version.
- func isIndirect(line *Line) bool {
- if len(line.Suffix) == 0 {
- return false
- }
- f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash)))
- return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;")
- }
- func (f *File) AddModuleStmt(path string) error {
- if f.Syntax == nil {
- f.Syntax = new(FileSyntax)
- }
- if f.Module == nil {
- f.Module = &Module{
- Mod: module.Version{Path: path},
- Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
- }
- } else {
- f.Module.Mod.Path = path
- f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
- }
- return nil
- }
- func (f *File) AddComment(text string) {
- if f.Syntax == nil {
- f.Syntax = new(FileSyntax)
- }
- f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
- Comments: Comments{
- Before: []Comment{
- {
- Token: text,
- },
- },
- },
- })
- }
- type VersionFixer func(path, version string) (string, error)
- // errDontFix is returned by a VersionFixer to indicate the version should be
- // left alone, even if it's not canonical.
- var dontFixRetract VersionFixer = func(_, vers string) (string, error) {
- return vers, nil
- }
- // Parse parses and returns a go.mod file.
- //
- // file is the name of the file, used in positions and errors.
- //
- // data is the content of the file.
- //
- // fix is an optional function that canonicalizes module versions.
- // If fix is nil, all module versions must be canonical (module.CanonicalVersion
- // must return the same string).
- func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
- return parseToFile(file, data, fix, true)
- }
- // ParseLax is like Parse but ignores unknown statements.
- // It is used when parsing go.mod files other than the main module,
- // under the theory that most statement types we add in the future will
- // only apply in the main module, like exclude and replace,
- // and so we get better gradual deployments if old go commands
- // simply ignore those statements when found in go.mod files
- // in dependencies.
- func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
- return parseToFile(file, data, fix, false)
- }
- func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) {
- fs, err := parse(file, data)
- if err != nil {
- return nil, err
- }
- f := &File{
- Syntax: fs,
- }
- var errs ErrorList
- // fix versions in retract directives after the file is parsed.
- // We need the module path to fix versions, and it might be at the end.
- defer func() {
- oldLen := len(errs)
- f.fixRetract(fix, &errs)
- if len(errs) > oldLen {
- parsed, err = nil, errs
- }
- }()
- for _, x := range fs.Stmt {
- switch x := x.(type) {
- case *Line:
- f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)
- case *LineBlock:
- if len(x.Token) > 1 {
- if strict {
- errs = append(errs, Error{
- Filename: file,
- Pos: x.Start,
- Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
- })
- }
- continue
- }
- switch x.Token[0] {
- default:
- if strict {
- errs = append(errs, Error{
- Filename: file,
- Pos: x.Start,
- Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
- })
- }
- continue
- case "module", "require", "exclude", "replace", "retract":
- for _, l := range x.Line {
- f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
- }
- }
- }
- }
- if len(errs) > 0 {
- return nil, errs
- }
- return f, nil
- }
- var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)
- var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
- // Toolchains must be named beginning with `go1`,
- // like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted.
- var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`)
- func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
- // If strict is false, this module is a dependency.
- // We ignore all unknown directives as well as main-module-only
- // directives like replace and exclude. It will work better for
- // forward compatibility if we can depend on modules that have unknown
- // statements (presumed relevant only when acting as the main module)
- // and simply ignore those statements.
- if !strict {
- switch verb {
- case "go", "module", "retract", "require":
- // want these even for dependency go.mods
- default:
- return
- }
- }
- wrapModPathError := func(modPath string, err error) {
- *errs = append(*errs, Error{
- Filename: f.Syntax.Name,
- Pos: line.Start,
- ModPath: modPath,
- Verb: verb,
- Err: err,
- })
- }
- wrapError := func(err error) {
- *errs = append(*errs, Error{
- Filename: f.Syntax.Name,
- Pos: line.Start,
- Err: err,
- })
- }
- errorf := func(format string, args ...interface{}) {
- wrapError(fmt.Errorf(format, args...))
- }
- switch verb {
- default:
- errorf("unknown directive: %s", verb)
- case "go":
- if f.Go != nil {
- errorf("repeated go statement")
- return
- }
- if len(args) != 1 {
- errorf("go directive expects exactly one argument")
- return
- } else if !GoVersionRE.MatchString(args[0]) {
- fixed := false
- if !strict {
- if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil {
- args[0] = m[1]
- fixed = true
- }
- }
- if !fixed {
- errorf("invalid go version '%s': must match format 1.23", args[0])
- return
- }
- }
- f.Go = &Go{Syntax: line}
- f.Go.Version = args[0]
- case "toolchain":
- if f.Toolchain != nil {
- errorf("repeated toolchain statement")
- return
- }
- if len(args) != 1 {
- errorf("toolchain directive expects exactly one argument")
- return
- } else if strict && !ToolchainRE.MatchString(args[0]) {
- errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0])
- return
- }
- f.Toolchain = &Toolchain{Syntax: line}
- f.Toolchain.Name = args[0]
- case "module":
- if f.Module != nil {
- errorf("repeated module statement")
- return
- }
- deprecated := parseDeprecation(block, line)
- f.Module = &Module{
- Syntax: line,
- Deprecated: deprecated,
- }
- if len(args) != 1 {
- errorf("usage: module module/path")
- return
- }
- s, err := parseString(&args[0])
- if err != nil {
- errorf("invalid quoted string: %v", err)
- return
- }
- f.Module.Mod = module.Version{Path: s}
- case "require", "exclude":
- if len(args) != 2 {
- errorf("usage: %s module/path v1.2.3", verb)
- return
- }
- s, err := parseString(&args[0])
- if err != nil {
- errorf("invalid quoted string: %v", err)
- return
- }
- v, err := parseVersion(verb, s, &args[1], fix)
- if err != nil {
- wrapError(err)
- return
- }
- pathMajor, err := modulePathMajor(s)
- if err != nil {
- wrapError(err)
- return
- }
- if err := module.CheckPathMajor(v, pathMajor); err != nil {
- wrapModPathError(s, err)
- return
- }
- if verb == "require" {
- f.Require = append(f.Require, &Require{
- Mod: module.Version{Path: s, Version: v},
- Syntax: line,
- Indirect: isIndirect(line),
- })
- } else {
- f.Exclude = append(f.Exclude, &Exclude{
- Mod: module.Version{Path: s, Version: v},
- Syntax: line,
- })
- }
- case "replace":
- replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
- if wrappederr != nil {
- *errs = append(*errs, *wrappederr)
- return
- }
- f.Replace = append(f.Replace, replace)
- case "retract":
- rationale := parseDirectiveComment(block, line)
- vi, err := parseVersionInterval(verb, "", &args, dontFixRetract)
- if err != nil {
- if strict {
- wrapError(err)
- return
- } else {
- // Only report errors parsing intervals in the main module. We may
- // support additional syntax in the future, such as open and half-open
- // intervals. Those can't be supported now, because they break the
- // go.mod parser, even in lax mode.
- return
- }
- }
- if len(args) > 0 && strict {
- // In the future, there may be additional information after the version.
- errorf("unexpected token after version: %q", args[0])
- return
- }
- retract := &Retract{
- VersionInterval: vi,
- Rationale: rationale,
- Syntax: line,
- }
- f.Retract = append(f.Retract, retract)
- }
- }
- func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) {
- wrapModPathError := func(modPath string, err error) *Error {
- return &Error{
- Filename: filename,
- Pos: line.Start,
- ModPath: modPath,
- Verb: verb,
- Err: err,
- }
- }
- wrapError := func(err error) *Error {
- return &Error{
- Filename: filename,
- Pos: line.Start,
- Err: err,
- }
- }
- errorf := func(format string, args ...interface{}) *Error {
- return wrapError(fmt.Errorf(format, args...))
- }
- arrow := 2
- if len(args) >= 2 && args[1] == "=>" {
- arrow = 1
- }
- if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
- 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)
- }
- s, err := parseString(&args[0])
- if err != nil {
- return nil, errorf("invalid quoted string: %v", err)
- }
- pathMajor, err := modulePathMajor(s)
- if err != nil {
- return nil, wrapModPathError(s, err)
- }
- var v string
- if arrow == 2 {
- v, err = parseVersion(verb, s, &args[1], fix)
- if err != nil {
- return nil, wrapError(err)
- }
- if err := module.CheckPathMajor(v, pathMajor); err != nil {
- return nil, wrapModPathError(s, err)
- }
- }
- ns, err := parseString(&args[arrow+1])
- if err != nil {
- return nil, errorf("invalid quoted string: %v", err)
- }
- nv := ""
- if len(args) == arrow+2 {
- if !IsDirectoryPath(ns) {
- if strings.Contains(ns, "@") {
- return nil, errorf("replacement module must match format 'path version', not 'path@version'")
- }
- return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
- }
- if filepath.Separator == '/' && strings.Contains(ns, `\`) {
- return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
- }
- }
- if len(args) == arrow+3 {
- nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
- if err != nil {
- return nil, wrapError(err)
- }
- if IsDirectoryPath(ns) {
- return nil, errorf("replacement module directory path %q cannot have version", ns)
- }
- }
- return &Replace{
- Old: module.Version{Path: s, Version: v},
- New: module.Version{Path: ns, Version: nv},
- Syntax: line,
- }, nil
- }
- // fixRetract applies fix to each retract directive in f, appending any errors
- // to errs.
- //
- // Most versions are fixed as we parse the file, but for retract directives,
- // the relevant module path is the one specified with the module directive,
- // and that might appear at the end of the file (or not at all).
- func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
- if fix == nil {
- return
- }
- path := ""
- if f.Module != nil {
- path = f.Module.Mod.Path
- }
- var r *Retract
- wrapError := func(err error) {
- *errs = append(*errs, Error{
- Filename: f.Syntax.Name,
- Pos: r.Syntax.Start,
- Err: err,
- })
- }
- for _, r = range f.Retract {
- if path == "" {
- wrapError(errors.New("no module directive found, so retract cannot be used"))
- return // only print the first one of these
- }
- args := r.Syntax.Token
- if args[0] == "retract" {
- args = args[1:]
- }
- vi, err := parseVersionInterval("retract", path, &args, fix)
- if err != nil {
- wrapError(err)
- }
- r.VersionInterval = vi
- }
- }
- func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
- wrapError := func(err error) {
- *errs = append(*errs, Error{
- Filename: f.Syntax.Name,
- Pos: line.Start,
- Err: err,
- })
- }
- errorf := func(format string, args ...interface{}) {
- wrapError(fmt.Errorf(format, args...))
- }
- switch verb {
- default:
- errorf("unknown directive: %s", verb)
- case "go":
- if f.Go != nil {
- errorf("repeated go statement")
- return
- }
- if len(args) != 1 {
- errorf("go directive expects exactly one argument")
- return
- } else if !GoVersionRE.MatchString(args[0]) {
- errorf("invalid go version '%s': must match format 1.23", args[0])
- return
- }
- f.Go = &Go{Syntax: line}
- f.Go.Version = args[0]
- case "toolchain":
- if f.Toolchain != nil {
- errorf("repeated toolchain statement")
- return
- }
- if len(args) != 1 {
- errorf("toolchain directive expects exactly one argument")
- return
- } else if !ToolchainRE.MatchString(args[0]) {
- errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0])
- return
- }
- f.Toolchain = &Toolchain{Syntax: line}
- f.Toolchain.Name = args[0]
- case "use":
- if len(args) != 1 {
- errorf("usage: %s local/dir", verb)
- return
- }
- s, err := parseString(&args[0])
- if err != nil {
- errorf("invalid quoted string: %v", err)
- return
- }
- f.Use = append(f.Use, &Use{
- Path: s,
- Syntax: line,
- })
- case "replace":
- replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
- if wrappederr != nil {
- *errs = append(*errs, *wrappederr)
- return
- }
- f.Replace = append(f.Replace, replace)
- }
- }
- // IsDirectoryPath reports whether the given path should be interpreted
- // as a directory path. Just like on the go command line, relative paths
- // and rooted paths are directory paths; the rest are module paths.
- func IsDirectoryPath(ns string) bool {
- // Because go.mod files can move from one system to another,
- // we check all known path syntaxes, both Unix and Windows.
- return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
- strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) ||
- len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
- }
- // MustQuote reports whether s must be quoted in order to appear as
- // a single token in a go.mod line.
- func MustQuote(s string) bool {
- for _, r := range s {
- switch r {
- case ' ', '"', '\'', '`':
- return true
- case '(', ')', '[', ']', '{', '}', ',':
- if len(s) > 1 {
- return true
- }
- default:
- if !unicode.IsPrint(r) {
- return true
- }
- }
- }
- return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
- }
- // AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
- // the quotation of s.
- func AutoQuote(s string) string {
- if MustQuote(s) {
- return strconv.Quote(s)
- }
- return s
- }
- func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) {
- toks := *args
- if len(toks) == 0 || toks[0] == "(" {
- return VersionInterval{}, fmt.Errorf("expected '[' or version")
- }
- if toks[0] != "[" {
- v, err := parseVersion(verb, path, &toks[0], fix)
- if err != nil {
- return VersionInterval{}, err
- }
- *args = toks[1:]
- return VersionInterval{Low: v, High: v}, nil
- }
- toks = toks[1:]
- if len(toks) == 0 {
- return VersionInterval{}, fmt.Errorf("expected version after '['")
- }
- low, err := parseVersion(verb, path, &toks[0], fix)
- if err != nil {
- return VersionInterval{}, err
- }
- toks = toks[1:]
- if len(toks) == 0 || toks[0] != "," {
- return VersionInterval{}, fmt.Errorf("expected ',' after version")
- }
- toks = toks[1:]
- if len(toks) == 0 {
- return VersionInterval{}, fmt.Errorf("expected version after ','")
- }
- high, err := parseVersion(verb, path, &toks[0], fix)
- if err != nil {
- return VersionInterval{}, err
- }
- toks = toks[1:]
- if len(toks) == 0 || toks[0] != "]" {
- return VersionInterval{}, fmt.Errorf("expected ']' after version")
- }
- toks = toks[1:]
- *args = toks
- return VersionInterval{Low: low, High: high}, nil
- }
- func parseString(s *string) (string, error) {
- t := *s
- if strings.HasPrefix(t, `"`) {
- var err error
- if t, err = strconv.Unquote(t); err != nil {
- return "", err
- }
- } else if strings.ContainsAny(t, "\"'`") {
- // Other quotes are reserved both for possible future expansion
- // and to avoid confusion. For example if someone types 'x'
- // we want that to be a syntax error and not a literal x in literal quotation marks.
- return "", fmt.Errorf("unquoted string cannot contain quote")
- }
- *s = AutoQuote(t)
- return t, nil
- }
- var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`)
- // parseDeprecation extracts the text of comments on a "module" directive and
- // extracts a deprecation message from that.
- //
- // A deprecation message is contained in a paragraph within a block of comments
- // that starts with "Deprecated:" (case sensitive). The message runs until the
- // end of the paragraph and does not include the "Deprecated:" prefix. If the
- // comment block has multiple paragraphs that start with "Deprecated:",
- // parseDeprecation returns the message from the first.
- func parseDeprecation(block *LineBlock, line *Line) string {
- text := parseDirectiveComment(block, line)
- m := deprecatedRE.FindStringSubmatch(text)
- if m == nil {
- return ""
- }
- return m[1]
- }
- // parseDirectiveComment extracts the text of comments on a directive.
- // If the directive's line does not have comments and is part of a block that
- // does have comments, the block's comments are used.
- func parseDirectiveComment(block *LineBlock, line *Line) string {
- comments := line.Comment()
- if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
- comments = block.Comment()
- }
- groups := [][]Comment{comments.Before, comments.Suffix}
- var lines []string
- for _, g := range groups {
- for _, c := range g {
- if !strings.HasPrefix(c.Token, "//") {
- continue // blank line
- }
- lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))
- }
- }
- return strings.Join(lines, "\n")
- }
- type ErrorList []Error
- func (e ErrorList) Error() string {
- errStrs := make([]string, len(e))
- for i, err := range e {
- errStrs[i] = err.Error()
- }
- return strings.Join(errStrs, "\n")
- }
- type Error struct {
- Filename string
- Pos Position
- Verb string
- ModPath string
- Err error
- }
- func (e *Error) Error() string {
- var pos string
- if e.Pos.LineRune > 1 {
- // Don't print LineRune if it's 1 (beginning of line).
- // It's always 1 except in scanner errors, which are rare.
- pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune)
- } else if e.Pos.Line > 0 {
- pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line)
- } else if e.Filename != "" {
- pos = fmt.Sprintf("%s: ", e.Filename)
- }
- var directive string
- if e.ModPath != "" {
- directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)
- } else if e.Verb != "" {
- directive = fmt.Sprintf("%s: ", e.Verb)
- }
- return pos + directive + e.Err.Error()
- }
- func (e *Error) Unwrap() error { return e.Err }
- func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
- t, err := parseString(s)
- if err != nil {
- return "", &Error{
- Verb: verb,
- ModPath: path,
- Err: &module.InvalidVersionError{
- Version: *s,
- Err: err,
- },
- }
- }
- if fix != nil {
- fixed, err := fix(path, t)
- if err != nil {
- if err, ok := err.(*module.ModuleError); ok {
- return "", &Error{
- Verb: verb,
- ModPath: path,
- Err: err.Err,
- }
- }
- return "", err
- }
- t = fixed
- } else {
- cv := module.CanonicalVersion(t)
- if cv == "" {
- return "", &Error{
- Verb: verb,
- ModPath: path,
- Err: &module.InvalidVersionError{
- Version: t,
- Err: errors.New("must be of the form v1.2.3"),
- },
- }
- }
- t = cv
- }
- *s = t
- return *s, nil
- }
- func modulePathMajor(path string) (string, error) {
- _, major, ok := module.SplitPathVersion(path)
- if !ok {
- return "", fmt.Errorf("invalid module path")
- }
- return major, nil
- }
- func (f *File) Format() ([]byte, error) {
- return Format(f.Syntax), nil
- }
- // Cleanup cleans up the file f after any edit operations.
- // To avoid quadratic behavior, modifications like DropRequire
- // clear the entry but do not remove it from the slice.
- // Cleanup cleans out all the cleared entries.
- func (f *File) Cleanup() {
- w := 0
- for _, r := range f.Require {
- if r.Mod.Path != "" {
- f.Require[w] = r
- w++
- }
- }
- f.Require = f.Require[:w]
- w = 0
- for _, x := range f.Exclude {
- if x.Mod.Path != "" {
- f.Exclude[w] = x
- w++
- }
- }
- f.Exclude = f.Exclude[:w]
- w = 0
- for _, r := range f.Replace {
- if r.Old.Path != "" {
- f.Replace[w] = r
- w++
- }
- }
- f.Replace = f.Replace[:w]
- w = 0
- for _, r := range f.Retract {
- if r.Low != "" || r.High != "" {
- f.Retract[w] = r
- w++
- }
- }
- f.Retract = f.Retract[:w]
- f.Syntax.Cleanup()
- }
- func (f *File) AddGoStmt(version string) error {
- if !GoVersionRE.MatchString(version) {
- return fmt.Errorf("invalid language version %q", version)
- }
- if f.Go == nil {
- var hint Expr
- if f.Module != nil && f.Module.Syntax != nil {
- hint = f.Module.Syntax
- }
- f.Go = &Go{
- Version: version,
- Syntax: f.Syntax.addLine(hint, "go", version),
- }
- } else {
- f.Go.Version = version
- f.Syntax.updateLine(f.Go.Syntax, "go", version)
- }
- return nil
- }
- // DropGoStmt deletes the go statement from the file.
- func (f *File) DropGoStmt() {
- if f.Go != nil {
- f.Go.Syntax.markRemoved()
- f.Go = nil
- }
- }
- // DropToolchainStmt deletes the toolchain statement from the file.
- func (f *File) DropToolchainStmt() {
- if f.Toolchain != nil {
- f.Toolchain.Syntax.markRemoved()
- f.Toolchain = nil
- }
- }
- func (f *File) AddToolchainStmt(name string) error {
- if !ToolchainRE.MatchString(name) {
- return fmt.Errorf("invalid toolchain name %q", name)
- }
- if f.Toolchain == nil {
- var hint Expr
- if f.Go != nil && f.Go.Syntax != nil {
- hint = f.Go.Syntax
- } else if f.Module != nil && f.Module.Syntax != nil {
- hint = f.Module.Syntax
- }
- f.Toolchain = &Toolchain{
- Name: name,
- Syntax: f.Syntax.addLine(hint, "toolchain", name),
- }
- } else {
- f.Toolchain.Name = name
- f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
- }
- return nil
- }
- // AddRequire sets the first require line for path to version vers,
- // preserving any existing comments for that line and removing all
- // other lines for path.
- //
- // If no line currently exists for path, AddRequire adds a new line
- // at the end of the last require block.
- func (f *File) AddRequire(path, vers string) error {
- need := true
- for _, r := range f.Require {
- if r.Mod.Path == path {
- if need {
- r.Mod.Version = vers
- f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
- need = false
- } else {
- r.Syntax.markRemoved()
- *r = Require{}
- }
- }
- }
- if need {
- f.AddNewRequire(path, vers, false)
- }
- return nil
- }
- // AddNewRequire adds a new require line for path at version vers at the end of
- // the last require block, regardless of any existing require lines for path.
- func (f *File) AddNewRequire(path, vers string, indirect bool) {
- line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
- r := &Require{
- Mod: module.Version{Path: path, Version: vers},
- Syntax: line,
- }
- r.setIndirect(indirect)
- f.Require = append(f.Require, r)
- }
- // SetRequire updates the requirements of f to contain exactly req, preserving
- // the existing block structure and line comment contents (except for 'indirect'
- // markings) for the first requirement on each named module path.
- //
- // The Syntax field is ignored for the requirements in req.
- //
- // Any requirements not already present in the file are added to the block
- // containing the last require line.
- //
- // The requirements in req must specify at most one distinct version for each
- // module path.
- //
- // If any existing requirements may be removed, the caller should call Cleanup
- // after all edits are complete.
- func (f *File) SetRequire(req []*Require) {
- type elem struct {
- version string
- indirect bool
- }
- need := make(map[string]elem)
- for _, r := range req {
- if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version {
- panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version))
- }
- need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect}
- }
- // Update or delete the existing Require entries to preserve
- // only the first for each module path in req.
- for _, r := range f.Require {
- e, ok := need[r.Mod.Path]
- if ok {
- r.setVersion(e.version)
- r.setIndirect(e.indirect)
- } else {
- r.markRemoved()
- }
- delete(need, r.Mod.Path)
- }
- // Add new entries in the last block of the file for any paths that weren't
- // already present.
- //
- // This step is nondeterministic, but the final result will be deterministic
- // because we will sort the block.
- for path, e := range need {
- f.AddNewRequire(path, e.version, e.indirect)
- }
- f.SortBlocks()
- }
- // SetRequireSeparateIndirect updates the requirements of f to contain the given
- // requirements. Comment contents (except for 'indirect' markings) are retained
- // from the first existing requirement for each module path. Like SetRequire,
- // SetRequireSeparateIndirect adds requirements for new paths in req,
- // updates the version and "// indirect" comment on existing requirements,
- // and deletes requirements on paths not in req. Existing duplicate requirements
- // are deleted.
- //
- // As its name suggests, SetRequireSeparateIndirect puts direct and indirect
- // requirements into two separate blocks, one containing only direct
- // requirements, and the other containing only indirect requirements.
- // SetRequireSeparateIndirect may move requirements between these two blocks
- // when their indirect markings change. However, SetRequireSeparateIndirect
- // won't move requirements from other blocks, especially blocks with comments.
- //
- // If the file initially has one uncommented block of requirements,
- // SetRequireSeparateIndirect will split it into a direct-only and indirect-only
- // block. This aids in the transition to separate blocks.
- func (f *File) SetRequireSeparateIndirect(req []*Require) {
- // hasComments returns whether a line or block has comments
- // other than "indirect".
- hasComments := func(c Comments) bool {
- return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 ||
- (len(c.Suffix) == 1 &&
- strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect")
- }
- // moveReq adds r to block. If r was in another block, moveReq deletes
- // it from that block and transfers its comments.
- moveReq := func(r *Require, block *LineBlock) {
- var line *Line
- if r.Syntax == nil {
- line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}}
- r.Syntax = line
- if r.Indirect {
- r.setIndirect(true)
- }
- } else {
- line = new(Line)
- *line = *r.Syntax
- if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" {
- line.Token = line.Token[1:]
- }
- r.Syntax.Token = nil // Cleanup will delete the old line.
- r.Syntax = line
- }
- line.InBlock = true
- block.Line = append(block.Line, line)
- }
- // Examine existing require lines and blocks.
- var (
- // We may insert new requirements into the last uncommented
- // direct-only and indirect-only blocks. We may also move requirements
- // to the opposite block if their indirect markings change.
- lastDirectIndex = -1
- lastIndirectIndex = -1
- // If there are no direct-only or indirect-only blocks, a new block may
- // be inserted after the last require line or block.
- lastRequireIndex = -1
- // If there's only one require line or block, and it's uncommented,
- // we'll move its requirements to the direct-only or indirect-only blocks.
- requireLineOrBlockCount = 0
- // Track the block each requirement belongs to (if any) so we can
- // move them later.
- lineToBlock = make(map[*Line]*LineBlock)
- )
- for i, stmt := range f.Syntax.Stmt {
- switch stmt := stmt.(type) {
- case *Line:
- if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
- continue
- }
- lastRequireIndex = i
- requireLineOrBlockCount++
- if !hasComments(stmt.Comments) {
- if isIndirect(stmt) {
- lastIndirectIndex = i
- } else {
- lastDirectIndex = i
- }
- }
- case *LineBlock:
- if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
- continue
- }
- lastRequireIndex = i
- requireLineOrBlockCount++
- allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
- allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments)
- for _, line := range stmt.Line {
- lineToBlock[line] = stmt
- if hasComments(line.Comments) {
- allDirect = false
- allIndirect = false
- } else if isIndirect(line) {
- allDirect = false
- } else {
- allIndirect = false
- }
- }
- if allDirect {
- lastDirectIndex = i
- }
- if allIndirect {
- lastIndirectIndex = i
- }
- }
- }
- oneFlatUncommentedBlock := requireLineOrBlockCount == 1 &&
- !hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment())
- // Create direct and indirect blocks if needed. Convert lines into blocks
- // if needed. If we end up with an empty block or a one-line block,
- // Cleanup will delete it or convert it to a line later.
- insertBlock := func(i int) *LineBlock {
- block := &LineBlock{Token: []string{"require"}}
- f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
- copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
- f.Syntax.Stmt[i] = block
- return block
- }
- ensureBlock := func(i int) *LineBlock {
- switch stmt := f.Syntax.Stmt[i].(type) {
- case *LineBlock:
- return stmt
- case *Line:
- block := &LineBlock{
- Token: []string{"require"},
- Line: []*Line{stmt},
- }
- stmt.Token = stmt.Token[1:] // remove "require"
- stmt.InBlock = true
- f.Syntax.Stmt[i] = block
- return block
- default:
- panic(fmt.Sprintf("unexpected statement: %v", stmt))
- }
- }
- var lastDirectBlock *LineBlock
- if lastDirectIndex < 0 {
- if lastIndirectIndex >= 0 {
- lastDirectIndex = lastIndirectIndex
- lastIndirectIndex++
- } else if lastRequireIndex >= 0 {
- lastDirectIndex = lastRequireIndex + 1
- } else {
- lastDirectIndex = len(f.Syntax.Stmt)
- }
- lastDirectBlock = insertBlock(lastDirectIndex)
- } else {
- lastDirectBlock = ensureBlock(lastDirectIndex)
- }
- var lastIndirectBlock *LineBlock
- if lastIndirectIndex < 0 {
- lastIndirectIndex = lastDirectIndex + 1
- lastIndirectBlock = insertBlock(lastIndirectIndex)
- } else {
- lastIndirectBlock = ensureBlock(lastIndirectIndex)
- }
- // Delete requirements we don't want anymore.
- // Update versions and indirect comments on requirements we want to keep.
- // If a requirement is in last{Direct,Indirect}Block with the wrong
- // indirect marking after this, or if the requirement is in an single
- // uncommented mixed block (oneFlatUncommentedBlock), move it to the
- // correct block.
- //
- // Some blocks may be empty after this. Cleanup will remove them.
- need := make(map[string]*Require)
- for _, r := range req {
- need[r.Mod.Path] = r
- }
- have := make(map[string]*Require)
- for _, r := range f.Require {
- path := r.Mod.Path
- if need[path] == nil || have[path] != nil {
- // Requirement not needed, or duplicate requirement. Delete.
- r.markRemoved()
- continue
- }
- have[r.Mod.Path] = r
- r.setVersion(need[path].Mod.Version)
- r.setIndirect(need[path].Indirect)
- if need[path].Indirect &&
- (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
- moveReq(r, lastIndirectBlock)
- } else if !need[path].Indirect &&
- (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
- moveReq(r, lastDirectBlock)
- }
- }
- // Add new requirements.
- for path, r := range need {
- if have[path] == nil {
- if r.Indirect {
- moveReq(r, lastIndirectBlock)
- } else {
- moveReq(r, lastDirectBlock)
- }
- f.Require = append(f.Require, r)
- }
- }
- f.SortBlocks()
- }
- func (f *File) DropRequire(path string) error {
- for _, r := range f.Require {
- if r.Mod.Path == path {
- r.Syntax.markRemoved()
- *r = Require{}
- }
- }
- return nil
- }
- // AddExclude adds a exclude statement to the mod file. Errors if the provided
- // version is not a canonical version string
- func (f *File) AddExclude(path, vers string) error {
- if err := checkCanonicalVersion(path, vers); err != nil {
- return err
- }
- var hint *Line
- for _, x := range f.Exclude {
- if x.Mod.Path == path && x.Mod.Version == vers {
- return nil
- }
- if x.Mod.Path == path {
- hint = x.Syntax
- }
- }
- f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
- return nil
- }
- func (f *File) DropExclude(path, vers string) error {
- for _, x := range f.Exclude {
- if x.Mod.Path == path && x.Mod.Version == vers {
- x.Syntax.markRemoved()
- *x = Exclude{}
- }
- }
- return nil
- }
- func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
- return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
- }
- func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
- need := true
- old := module.Version{Path: oldPath, Version: oldVers}
- new := module.Version{Path: newPath, Version: newVers}
- tokens := []string{"replace", AutoQuote(oldPath)}
- if oldVers != "" {
- tokens = append(tokens, oldVers)
- }
- tokens = append(tokens, "=>", AutoQuote(newPath))
- if newVers != "" {
- tokens = append(tokens, newVers)
- }
- var hint *Line
- for _, r := range *replace {
- if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
- if need {
- // Found replacement for old; update to use new.
- r.New = new
- syntax.updateLine(r.Syntax, tokens...)
- need = false
- continue
- }
- // Already added; delete other replacements for same.
- r.Syntax.markRemoved()
- *r = Replace{}
- }
- if r.Old.Path == oldPath {
- hint = r.Syntax
- }
- }
- if need {
- *replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
- }
- return nil
- }
- func (f *File) DropReplace(oldPath, oldVers string) error {
- for _, r := range f.Replace {
- if r.Old.Path == oldPath && r.Old.Version == oldVers {
- r.Syntax.markRemoved()
- *r = Replace{}
- }
- }
- return nil
- }
- // AddRetract adds a retract statement to the mod file. Errors if the provided
- // version interval does not consist of canonical version strings
- func (f *File) AddRetract(vi VersionInterval, rationale string) error {
- var path string
- if f.Module != nil {
- path = f.Module.Mod.Path
- }
- if err := checkCanonicalVersion(path, vi.High); err != nil {
- return err
- }
- if err := checkCanonicalVersion(path, vi.Low); err != nil {
- return err
- }
- r := &Retract{
- VersionInterval: vi,
- }
- if vi.Low == vi.High {
- r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))
- } else {
- r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
- }
- if rationale != "" {
- for _, line := range strings.Split(rationale, "\n") {
- com := Comment{Token: "// " + line}
- r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
- }
- }
- return nil
- }
- func (f *File) DropRetract(vi VersionInterval) error {
- for _, r := range f.Retract {
- if r.VersionInterval == vi {
- r.Syntax.markRemoved()
- *r = Retract{}
- }
- }
- return nil
- }
- func (f *File) SortBlocks() {
- f.removeDups() // otherwise sorting is unsafe
- // semanticSortForExcludeVersionV is the Go version (plus leading "v") at which
- // lines in exclude blocks start to use semantic sort instead of lexicographic sort.
- // See go.dev/issue/60028.
- const semanticSortForExcludeVersionV = "v1.21"
- useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0
- for _, stmt := range f.Syntax.Stmt {
- block, ok := stmt.(*LineBlock)
- if !ok {
- continue
- }
- less := lineLess
- if block.Token[0] == "exclude" && useSemanticSortForExclude {
- less = lineExcludeLess
- } else if block.Token[0] == "retract" {
- less = lineRetractLess
- }
- sort.SliceStable(block.Line, func(i, j int) bool {
- return less(block.Line[i], block.Line[j])
- })
- }
- }
- // removeDups removes duplicate exclude and replace directives.
- //
- // Earlier exclude directives take priority.
- //
- // Later replace directives take priority.
- //
- // require directives are not de-duplicated. That's left up to higher-level
- // logic (MVS).
- //
- // 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)
- }
- func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
- kill := make(map[*Line]bool)
- // Remove duplicate excludes.
- if exclude != nil {
- haveExclude := make(map[module.Version]bool)
- for _, x := range *exclude {
- if haveExclude[x.Mod] {
- kill[x.Syntax] = true
- continue
- }
- haveExclude[x.Mod] = true
- }
- var excl []*Exclude
- for _, x := range *exclude {
- if !kill[x.Syntax] {
- excl = append(excl, x)
- }
- }
- *exclude = excl
- }
- // Remove duplicate replacements.
- // Later replacements take priority over earlier ones.
- haveReplace := make(map[module.Version]bool)
- for i := len(*replace) - 1; i >= 0; i-- {
- x := (*replace)[i]
- if haveReplace[x.Old] {
- kill[x.Syntax] = true
- continue
- }
- haveReplace[x.Old] = true
- }
- var repl []*Replace
- for _, x := range *replace {
- if !kill[x.Syntax] {
- repl = append(repl, x)
- }
- }
- *replace = repl
- // Duplicate require and retract directives are not removed.
- // Drop killed statements from the syntax tree.
- var stmts []Expr
- for _, stmt := range syntax.Stmt {
- switch stmt := stmt.(type) {
- case *Line:
- if kill[stmt] {
- continue
- }
- case *LineBlock:
- var lines []*Line
- for _, line := range stmt.Line {
- if !kill[line] {
- lines = append(lines, line)
- }
- }
- stmt.Line = lines
- if len(lines) == 0 {
- continue
- }
- }
- stmts = append(stmts, stmt)
- }
- 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 {
- 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 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 {
- if len(li.Token) != 2 || len(lj.Token) != 2 {
- // Not a known exclude specification.
- // Fall back to sorting lexicographically.
- return lineLess(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 semver.Compare(li.Token[1], lj.Token[1]) < 0
- }
- // 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.
- // Intervals are sorted in descending order, first by low version, then by
- // high version, using semver.Compare.
- func lineRetractLess(li, lj *Line) bool {
- interval := func(l *Line) VersionInterval {
- if len(l.Token) == 1 {
- return VersionInterval{Low: l.Token[0], High: l.Token[0]}
- } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {
- return VersionInterval{Low: l.Token[1], High: l.Token[3]}
- } else {
- // Line in unknown format. Treat as an invalid version.
- return VersionInterval{}
- }
- }
- vii := interval(li)
- vij := interval(lj)
- if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {
- return cmp > 0
- }
- return semver.Compare(vii.High, vij.High) > 0
- }
- // checkCanonicalVersion returns a non-nil error if vers is not a canonical
- // version string or does not match the major version of path.
- //
- // If path is non-empty, the error text suggests a format with a major version
- // corresponding to the path.
- func checkCanonicalVersion(path, vers string) error {
- _, pathMajor, pathMajorOk := module.SplitPathVersion(path)
- if vers == "" || vers != module.CanonicalVersion(vers) {
- if pathMajor == "" {
- return &module.InvalidVersionError{
- Version: vers,
- Err: fmt.Errorf("must be of the form v1.2.3"),
- }
- }
- return &module.InvalidVersionError{
- Version: vers,
- Err: fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)),
- }
- }
- if pathMajorOk {
- if err := module.CheckPathMajor(vers, pathMajor); err != nil {
- if pathMajor == "" {
- // In this context, the user probably wrote "v2.3.4" when they meant
- // "v2.3.4+incompatible". Suggest that instead of "v0 or v1".
- return &module.InvalidVersionError{
- Version: vers,
- Err: fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)),
- }
- }
- return err
- }
- }
- return nil
- }
|