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