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