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