github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/internal/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 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "path/filepath" 12 "regexp" 13 "sort" 14 "strconv" 15 "strings" 16 "unicode" 17 18 "cmd/go/internal/module" 19 "cmd/go/internal/semver" 20 ) 21 22 // A File is the parsed, interpreted form of a go.mod file. 23 type File struct { 24 Module *Module 25 Go *Go 26 Require []*Require 27 Exclude []*Exclude 28 Replace []*Replace 29 30 Syntax *FileSyntax 31 } 32 33 // A Module is the module statement. 34 type Module struct { 35 Mod module.Version 36 Syntax *Line 37 } 38 39 // A Go is the go statement. 40 type Go struct { 41 Version string // "1.23" 42 Syntax *Line 43 } 44 45 // A Require is a single require statement. 46 type Require struct { 47 Mod module.Version 48 Indirect bool // has "// indirect" comment 49 Syntax *Line 50 } 51 52 // An Exclude is a single exclude statement. 53 type Exclude struct { 54 Mod module.Version 55 Syntax *Line 56 } 57 58 // A Replace is a single replace statement. 59 type Replace struct { 60 Old module.Version 61 New module.Version 62 Syntax *Line 63 } 64 65 func (f *File) AddModuleStmt(path string) error { 66 if f.Syntax == nil { 67 f.Syntax = new(FileSyntax) 68 } 69 if f.Module == nil { 70 f.Module = &Module{ 71 Mod: module.Version{Path: path}, 72 Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), 73 } 74 } else { 75 f.Module.Mod.Path = path 76 f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) 77 } 78 return nil 79 } 80 81 func (f *File) AddComment(text string) { 82 if f.Syntax == nil { 83 f.Syntax = new(FileSyntax) 84 } 85 f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ 86 Comments: Comments{ 87 Before: []Comment{ 88 { 89 Token: text, 90 }, 91 }, 92 }, 93 }) 94 } 95 96 type VersionFixer func(path, version string) (string, error) 97 98 // Parse parses the data, reported in errors as being from file, 99 // into a File struct. It applies fix, if non-nil, to canonicalize all module versions found. 100 func Parse(file string, data []byte, fix VersionFixer) (*File, error) { 101 return parseToFile(file, data, fix, true) 102 } 103 104 // ParseLax is like Parse but ignores unknown statements. 105 // It is used when parsing go.mod files other than the main module, 106 // under the theory that most statement types we add in the future will 107 // only apply in the main module, like exclude and replace, 108 // and so we get better gradual deployments if old go commands 109 // simply ignore those statements when found in go.mod files 110 // in dependencies. 111 func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { 112 return parseToFile(file, data, fix, false) 113 } 114 115 func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) { 116 fs, err := parse(file, data) 117 if err != nil { 118 return nil, err 119 } 120 f := &File{ 121 Syntax: fs, 122 } 123 124 var errs bytes.Buffer 125 for _, x := range fs.Stmt { 126 switch x := x.(type) { 127 case *Line: 128 f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict) 129 130 case *LineBlock: 131 if len(x.Token) > 1 { 132 if strict { 133 fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) 134 } 135 continue 136 } 137 switch x.Token[0] { 138 default: 139 if strict { 140 fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) 141 } 142 continue 143 case "module", "require", "exclude", "replace": 144 for _, l := range x.Line { 145 f.add(&errs, l, x.Token[0], l.Token, fix, strict) 146 } 147 } 148 } 149 } 150 151 if errs.Len() > 0 { 152 return nil, errors.New(strings.TrimRight(errs.String(), "\n")) 153 } 154 return f, nil 155 } 156 157 var GoVersionRE = regexp.MustCompile(`([1-9][0-9]*)\.(0|[1-9][0-9]*)`) 158 159 func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) { 160 // If strict is false, this module is a dependency. 161 // We ignore all unknown directives as well as main-module-only 162 // directives like replace and exclude. It will work better for 163 // forward compatibility if we can depend on modules that have unknown 164 // statements (presumed relevant only when acting as the main module) 165 // and simply ignore those statements. 166 if !strict { 167 switch verb { 168 case "module", "require", "go": 169 // want these even for dependency go.mods 170 default: 171 return 172 } 173 } 174 175 switch verb { 176 default: 177 fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb) 178 179 case "go": 180 if f.Go != nil { 181 fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line) 182 return 183 } 184 if len(args) != 1 || !GoVersionRE.MatchString(args[0]) { 185 fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line) 186 return 187 } 188 f.Go = &Go{Syntax: line} 189 f.Go.Version = args[0] 190 case "module": 191 if f.Module != nil { 192 fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line) 193 return 194 } 195 f.Module = &Module{Syntax: line} 196 if len(args) != 1 { 197 198 fmt.Fprintf(errs, "%s:%d: usage: module module/path [version]\n", f.Syntax.Name, line.Start.Line) 199 return 200 } 201 s, err := parseString(&args[0]) 202 if err != nil { 203 fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) 204 return 205 } 206 f.Module.Mod = module.Version{Path: s} 207 case "require", "exclude": 208 if len(args) != 2 { 209 fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3\n", f.Syntax.Name, line.Start.Line, verb) 210 return 211 } 212 s, err := parseString(&args[0]) 213 if err != nil { 214 fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) 215 return 216 } 217 old := args[1] 218 v, err := parseVersion(s, &args[1], fix) 219 if err != nil { 220 fmt.Fprintf(errs, "%s:%d: invalid module version %q: %v\n", f.Syntax.Name, line.Start.Line, old, err) 221 return 222 } 223 pathMajor, err := modulePathMajor(s) 224 if err != nil { 225 fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) 226 return 227 } 228 if !module.MatchPathMajor(v, pathMajor) { 229 if pathMajor == "" { 230 pathMajor = "v0 or v1" 231 } 232 fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, pathMajor, semver.Major(v), v) 233 return 234 } 235 if verb == "require" { 236 f.Require = append(f.Require, &Require{ 237 Mod: module.Version{Path: s, Version: v}, 238 Syntax: line, 239 Indirect: isIndirect(line), 240 }) 241 } else { 242 f.Exclude = append(f.Exclude, &Exclude{ 243 Mod: module.Version{Path: s, Version: v}, 244 Syntax: line, 245 }) 246 } 247 case "replace": 248 arrow := 2 249 if len(args) >= 2 && args[1] == "=>" { 250 arrow = 1 251 } 252 if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { 253 fmt.Fprintf(errs, "%s:%d: usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory\n", f.Syntax.Name, line.Start.Line, verb, verb) 254 return 255 } 256 s, err := parseString(&args[0]) 257 if err != nil { 258 fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) 259 return 260 } 261 pathMajor, err := modulePathMajor(s) 262 if err != nil { 263 fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) 264 return 265 } 266 var v string 267 if arrow == 2 { 268 old := args[1] 269 v, err = parseVersion(s, &args[1], fix) 270 if err != nil { 271 fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err) 272 return 273 } 274 if !module.MatchPathMajor(v, pathMajor) { 275 if pathMajor == "" { 276 pathMajor = "v0 or v1" 277 } 278 fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, pathMajor, semver.Major(v), v) 279 return 280 } 281 } 282 ns, err := parseString(&args[arrow+1]) 283 if err != nil { 284 fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) 285 return 286 } 287 nv := "" 288 if len(args) == arrow+2 { 289 if !IsDirectoryPath(ns) { 290 fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)\n", f.Syntax.Name, line.Start.Line) 291 return 292 } 293 if filepath.Separator == '/' && strings.Contains(ns, `\`) { 294 fmt.Fprintf(errs, "%s:%d: replacement directory appears to be Windows path (on a non-windows system)\n", f.Syntax.Name, line.Start.Line) 295 return 296 } 297 } 298 if len(args) == arrow+3 { 299 old := args[arrow+1] 300 nv, err = parseVersion(ns, &args[arrow+2], fix) 301 if err != nil { 302 fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err) 303 return 304 } 305 if IsDirectoryPath(ns) { 306 fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version\n", f.Syntax.Name, line.Start.Line, ns) 307 return 308 } 309 } 310 f.Replace = append(f.Replace, &Replace{ 311 Old: module.Version{Path: s, Version: v}, 312 New: module.Version{Path: ns, Version: nv}, 313 Syntax: line, 314 }) 315 } 316 } 317 318 // isIndirect reports whether line has a "// indirect" comment, 319 // meaning it is in go.mod only for its effect on indirect dependencies, 320 // so that it can be dropped entirely once the effective version of the 321 // indirect dependency reaches the given minimum version. 322 func isIndirect(line *Line) bool { 323 if len(line.Suffix) == 0 { 324 return false 325 } 326 f := strings.Fields(line.Suffix[0].Token) 327 return (len(f) == 2 && f[1] == "indirect" || len(f) > 2 && f[1] == "indirect;") && f[0] == "//" 328 } 329 330 // setIndirect sets line to have (or not have) a "// indirect" comment. 331 func setIndirect(line *Line, indirect bool) { 332 if isIndirect(line) == indirect { 333 return 334 } 335 if indirect { 336 // Adding comment. 337 if len(line.Suffix) == 0 { 338 // New comment. 339 line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} 340 return 341 } 342 // Insert at beginning of existing comment. 343 com := &line.Suffix[0] 344 space := " " 345 if len(com.Token) > 2 && com.Token[2] == ' ' || com.Token[2] == '\t' { 346 space = "" 347 } 348 com.Token = "// indirect;" + space + com.Token[2:] 349 return 350 } 351 352 // Removing comment. 353 f := strings.Fields(line.Suffix[0].Token) 354 if len(f) == 2 { 355 // Remove whole comment. 356 line.Suffix = nil 357 return 358 } 359 360 // Remove comment prefix. 361 com := &line.Suffix[0] 362 i := strings.Index(com.Token, "indirect;") 363 com.Token = "//" + com.Token[i+len("indirect;"):] 364 } 365 366 // IsDirectoryPath reports whether the given path should be interpreted 367 // as a directory path. Just like on the go command line, relative paths 368 // and rooted paths are directory paths; the rest are module paths. 369 func IsDirectoryPath(ns string) bool { 370 // Because go.mod files can move from one system to another, 371 // we check all known path syntaxes, both Unix and Windows. 372 return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") || 373 strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) || 374 len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' 375 } 376 377 // MustQuote reports whether s must be quoted in order to appear as 378 // a single token in a go.mod line. 379 func MustQuote(s string) bool { 380 for _, r := range s { 381 if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' { 382 return true 383 } 384 } 385 return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") 386 } 387 388 // AutoQuote returns s or, if quoting is required for s to appear in a go.mod, 389 // the quotation of s. 390 func AutoQuote(s string) string { 391 if MustQuote(s) { 392 return strconv.Quote(s) 393 } 394 return s 395 } 396 397 func parseString(s *string) (string, error) { 398 t := *s 399 if strings.HasPrefix(t, `"`) { 400 var err error 401 if t, err = strconv.Unquote(t); err != nil { 402 return "", err 403 } 404 } else if strings.ContainsAny(t, "\"'`") { 405 // Other quotes are reserved both for possible future expansion 406 // and to avoid confusion. For example if someone types 'x' 407 // we want that to be a syntax error and not a literal x in literal quotation marks. 408 return "", fmt.Errorf("unquoted string cannot contain quote") 409 } 410 *s = AutoQuote(t) 411 return t, nil 412 } 413 414 func parseVersion(path string, s *string, fix VersionFixer) (string, error) { 415 t, err := parseString(s) 416 if err != nil { 417 return "", err 418 } 419 if fix != nil { 420 var err error 421 t, err = fix(path, t) 422 if err != nil { 423 return "", err 424 } 425 } 426 if v := module.CanonicalVersion(t); v != "" { 427 *s = v 428 return *s, nil 429 } 430 return "", fmt.Errorf("version must be of the form v1.2.3") 431 } 432 433 func modulePathMajor(path string) (string, error) { 434 _, major, ok := module.SplitPathVersion(path) 435 if !ok { 436 return "", fmt.Errorf("invalid module path") 437 } 438 return major, nil 439 } 440 441 func (f *File) Format() ([]byte, error) { 442 return Format(f.Syntax), nil 443 } 444 445 // Cleanup cleans up the file f after any edit operations. 446 // To avoid quadratic behavior, modifications like DropRequire 447 // clear the entry but do not remove it from the slice. 448 // Cleanup cleans out all the cleared entries. 449 func (f *File) Cleanup() { 450 w := 0 451 for _, r := range f.Require { 452 if r.Mod.Path != "" { 453 f.Require[w] = r 454 w++ 455 } 456 } 457 f.Require = f.Require[:w] 458 459 w = 0 460 for _, x := range f.Exclude { 461 if x.Mod.Path != "" { 462 f.Exclude[w] = x 463 w++ 464 } 465 } 466 f.Exclude = f.Exclude[:w] 467 468 w = 0 469 for _, r := range f.Replace { 470 if r.Old.Path != "" { 471 f.Replace[w] = r 472 w++ 473 } 474 } 475 f.Replace = f.Replace[:w] 476 477 f.Syntax.Cleanup() 478 } 479 480 func (f *File) AddGoStmt(version string) error { 481 if !GoVersionRE.MatchString(version) { 482 return fmt.Errorf("invalid language version string %q", version) 483 } 484 if f.Go == nil { 485 f.Go = &Go{ 486 Version: version, 487 Syntax: f.Syntax.addLine(nil, "go", version), 488 } 489 } else { 490 f.Go.Version = version 491 f.Syntax.updateLine(f.Go.Syntax, "go", version) 492 } 493 return nil 494 } 495 496 func (f *File) AddRequire(path, vers string) error { 497 need := true 498 for _, r := range f.Require { 499 if r.Mod.Path == path { 500 if need { 501 r.Mod.Version = vers 502 f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) 503 need = false 504 } else { 505 f.Syntax.removeLine(r.Syntax) 506 *r = Require{} 507 } 508 } 509 } 510 511 if need { 512 f.AddNewRequire(path, vers, false) 513 } 514 return nil 515 } 516 517 func (f *File) AddNewRequire(path, vers string, indirect bool) { 518 line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) 519 setIndirect(line, indirect) 520 f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line}) 521 } 522 523 func (f *File) SetRequire(req []*Require) { 524 need := make(map[string]string) 525 indirect := make(map[string]bool) 526 for _, r := range req { 527 need[r.Mod.Path] = r.Mod.Version 528 indirect[r.Mod.Path] = r.Indirect 529 } 530 531 for _, r := range f.Require { 532 if v, ok := need[r.Mod.Path]; ok { 533 r.Mod.Version = v 534 r.Indirect = indirect[r.Mod.Path] 535 } 536 } 537 538 var newStmts []Expr 539 for _, stmt := range f.Syntax.Stmt { 540 switch stmt := stmt.(type) { 541 case *LineBlock: 542 if len(stmt.Token) > 0 && stmt.Token[0] == "require" { 543 var newLines []*Line 544 for _, line := range stmt.Line { 545 if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" { 546 line.Token[1] = need[p] 547 delete(need, p) 548 setIndirect(line, indirect[p]) 549 newLines = append(newLines, line) 550 } 551 } 552 if len(newLines) == 0 { 553 continue // drop stmt 554 } 555 stmt.Line = newLines 556 } 557 558 case *Line: 559 if len(stmt.Token) > 0 && stmt.Token[0] == "require" { 560 if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" { 561 stmt.Token[2] = need[p] 562 delete(need, p) 563 setIndirect(stmt, indirect[p]) 564 } else { 565 continue // drop stmt 566 } 567 } 568 } 569 newStmts = append(newStmts, stmt) 570 } 571 f.Syntax.Stmt = newStmts 572 573 for path, vers := range need { 574 f.AddNewRequire(path, vers, indirect[path]) 575 } 576 f.SortBlocks() 577 } 578 579 func (f *File) DropRequire(path string) error { 580 for _, r := range f.Require { 581 if r.Mod.Path == path { 582 f.Syntax.removeLine(r.Syntax) 583 *r = Require{} 584 } 585 } 586 return nil 587 } 588 589 func (f *File) AddExclude(path, vers string) error { 590 var hint *Line 591 for _, x := range f.Exclude { 592 if x.Mod.Path == path && x.Mod.Version == vers { 593 return nil 594 } 595 if x.Mod.Path == path { 596 hint = x.Syntax 597 } 598 } 599 600 f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) 601 return nil 602 } 603 604 func (f *File) DropExclude(path, vers string) error { 605 for _, x := range f.Exclude { 606 if x.Mod.Path == path && x.Mod.Version == vers { 607 f.Syntax.removeLine(x.Syntax) 608 *x = Exclude{} 609 } 610 } 611 return nil 612 } 613 614 func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { 615 need := true 616 old := module.Version{Path: oldPath, Version: oldVers} 617 new := module.Version{Path: newPath, Version: newVers} 618 tokens := []string{"replace", AutoQuote(oldPath)} 619 if oldVers != "" { 620 tokens = append(tokens, oldVers) 621 } 622 tokens = append(tokens, "=>", AutoQuote(newPath)) 623 if newVers != "" { 624 tokens = append(tokens, newVers) 625 } 626 627 var hint *Line 628 for _, r := range f.Replace { 629 if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { 630 if need { 631 // Found replacement for old; update to use new. 632 r.New = new 633 f.Syntax.updateLine(r.Syntax, tokens...) 634 need = false 635 continue 636 } 637 // Already added; delete other replacements for same. 638 f.Syntax.removeLine(r.Syntax) 639 *r = Replace{} 640 } 641 if r.Old.Path == oldPath { 642 hint = r.Syntax 643 } 644 } 645 if need { 646 f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)}) 647 } 648 return nil 649 } 650 651 func (f *File) DropReplace(oldPath, oldVers string) error { 652 for _, r := range f.Replace { 653 if r.Old.Path == oldPath && r.Old.Version == oldVers { 654 f.Syntax.removeLine(r.Syntax) 655 *r = Replace{} 656 } 657 } 658 return nil 659 } 660 661 func (f *File) SortBlocks() { 662 f.removeDups() // otherwise sorting is unsafe 663 664 for _, stmt := range f.Syntax.Stmt { 665 block, ok := stmt.(*LineBlock) 666 if !ok { 667 continue 668 } 669 sort.Slice(block.Line, func(i, j int) bool { 670 li := block.Line[i] 671 lj := block.Line[j] 672 for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { 673 if li.Token[k] != lj.Token[k] { 674 return li.Token[k] < lj.Token[k] 675 } 676 } 677 return len(li.Token) < len(lj.Token) 678 }) 679 } 680 } 681 682 func (f *File) removeDups() { 683 have := make(map[module.Version]bool) 684 kill := make(map[*Line]bool) 685 for _, x := range f.Exclude { 686 if have[x.Mod] { 687 kill[x.Syntax] = true 688 continue 689 } 690 have[x.Mod] = true 691 } 692 var excl []*Exclude 693 for _, x := range f.Exclude { 694 if !kill[x.Syntax] { 695 excl = append(excl, x) 696 } 697 } 698 f.Exclude = excl 699 700 have = make(map[module.Version]bool) 701 // Later replacements take priority over earlier ones. 702 for i := len(f.Replace) - 1; i >= 0; i-- { 703 x := f.Replace[i] 704 if have[x.Old] { 705 kill[x.Syntax] = true 706 continue 707 } 708 have[x.Old] = true 709 } 710 var repl []*Replace 711 for _, x := range f.Replace { 712 if !kill[x.Syntax] { 713 repl = append(repl, x) 714 } 715 } 716 f.Replace = repl 717 718 var stmts []Expr 719 for _, stmt := range f.Syntax.Stmt { 720 switch stmt := stmt.(type) { 721 case *Line: 722 if kill[stmt] { 723 continue 724 } 725 case *LineBlock: 726 var lines []*Line 727 for _, line := range stmt.Line { 728 if !kill[line] { 729 lines = append(lines, line) 730 } 731 } 732 stmt.Line = lines 733 if len(lines) == 0 { 734 continue 735 } 736 } 737 stmts = append(stmts, stmt) 738 } 739 f.Syntax.Stmt = stmts 740 }