Skip to content

Commit 4029241

Browse files
committed
[dev.cmdgo] modfile: parser changes for workfile proposal
This change adds interfaces to the mod package to support parsing and representing go.work files, including WorkFile for the file as a whole, Directory for directory statements, and ParseWork for parsing go.work files. This code is mostly a lightly modified version of the parsing code for mod files. This is meant to support the workspaces proposal and if the proposal is accepted it's expected theinterface will likely change. For #45713 Change-Id: I5df6fe4acba1dbe86bc3e3fba40a04fbb4d678e4 Reviewed-on: https://go-review.googlesource.com/c/mod/+/336089 Trust: Michael Matloob <[email protected]> Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Jay Conrod <[email protected]> (cherry picked from commit e41a6a4) Reviewed-on: https://go-review.googlesource.com/c/mod/+/324764 Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 607370a commit 4029241

14 files changed

+884
-81
lines changed

modfile/read_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ func TestPrintParse(t *testing.T) {
152152
for _, out := range outs {
153153
out := out
154154
name := filepath.Base(out)
155+
if !strings.HasSuffix(out, ".in") && !strings.HasSuffix(out, ".golden") {
156+
continue
157+
}
155158
t.Run(name, func(t *testing.T) {
156159
t.Parallel()
157160
data, err := ioutil.ReadFile(out)

modfile/rule.go

Lines changed: 169 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -423,68 +423,12 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
423423
}
424424

425425
case "replace":
426-
arrow := 2
427-
if len(args) >= 2 && args[1] == "=>" {
428-
arrow = 1
429-
}
430-
if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
431-
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)
432-
return
433-
}
434-
s, err := parseString(&args[0])
435-
if err != nil {
436-
errorf("invalid quoted string: %v", err)
437-
return
438-
}
439-
pathMajor, err := modulePathMajor(s)
440-
if err != nil {
441-
wrapModPathError(s, err)
442-
return
443-
}
444-
var v string
445-
if arrow == 2 {
446-
v, err = parseVersion(verb, s, &args[1], fix)
447-
if err != nil {
448-
wrapError(err)
449-
return
450-
}
451-
if err := module.CheckPathMajor(v, pathMajor); err != nil {
452-
wrapModPathError(s, err)
453-
return
454-
}
455-
}
456-
ns, err := parseString(&args[arrow+1])
457-
if err != nil {
458-
errorf("invalid quoted string: %v", err)
426+
replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
427+
if wrappederr != nil {
428+
*errs = append(*errs, *wrappederr)
459429
return
460430
}
461-
nv := ""
462-
if len(args) == arrow+2 {
463-
if !IsDirectoryPath(ns) {
464-
errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
465-
return
466-
}
467-
if filepath.Separator == '/' && strings.Contains(ns, `\`) {
468-
errorf("replacement directory appears to be Windows path (on a non-windows system)")
469-
return
470-
}
471-
}
472-
if len(args) == arrow+3 {
473-
nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
474-
if err != nil {
475-
wrapError(err)
476-
return
477-
}
478-
if IsDirectoryPath(ns) {
479-
errorf("replacement module directory path %q cannot have version", ns)
480-
return
481-
}
482-
}
483-
f.Replace = append(f.Replace, &Replace{
484-
Old: module.Version{Path: s, Version: v},
485-
New: module.Version{Path: ns, Version: nv},
486-
Syntax: line,
487-
})
431+
f.Replace = append(f.Replace, replace)
488432

489433
case "retract":
490434
rationale := parseDirectiveComment(block, line)
@@ -515,6 +459,83 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
515459
}
516460
}
517461

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+
return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
517+
}
518+
if filepath.Separator == '/' && strings.Contains(ns, `\`) {
519+
return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
520+
}
521+
}
522+
if len(args) == arrow+3 {
523+
nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
524+
if err != nil {
525+
return nil, wrapError(err)
526+
}
527+
if IsDirectoryPath(ns) {
528+
return nil, errorf("replacement module directory path %q cannot have version", ns)
529+
530+
}
531+
}
532+
return &Replace{
533+
Old: module.Version{Path: s, Version: v},
534+
New: module.Version{Path: ns, Version: nv},
535+
Syntax: line,
536+
}, nil
537+
}
538+
518539
// fixRetract applies fix to each retract directive in f, appending any errors
519540
// to errs.
520541
//
@@ -556,6 +577,63 @@ func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
556577
}
557578
}
558579

580+
func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
581+
wrapError := func(err error) {
582+
*errs = append(*errs, Error{
583+
Filename: f.Syntax.Name,
584+
Pos: line.Start,
585+
Err: err,
586+
})
587+
}
588+
errorf := func(format string, args ...interface{}) {
589+
wrapError(fmt.Errorf(format, args...))
590+
}
591+
592+
switch verb {
593+
default:
594+
errorf("unknown directive: %s", verb)
595+
596+
case "go":
597+
if f.Go != nil {
598+
errorf("repeated go statement")
599+
return
600+
}
601+
if len(args) != 1 {
602+
errorf("go directive expects exactly one argument")
603+
return
604+
} else if !GoVersionRE.MatchString(args[0]) {
605+
errorf("invalid go version '%s': must match format 1.23", args[0])
606+
return
607+
}
608+
609+
f.Go = &Go{Syntax: line}
610+
f.Go.Version = args[0]
611+
612+
case "directory":
613+
if len(args) != 1 {
614+
errorf("usage: %s local/dir", verb)
615+
return
616+
}
617+
s, err := parseString(&args[0])
618+
if err != nil {
619+
errorf("invalid quoted string: %v", err)
620+
return
621+
}
622+
f.Directory = append(f.Directory, &Directory{
623+
Path: s,
624+
Syntax: line,
625+
})
626+
627+
case "replace":
628+
replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
629+
if wrappederr != nil {
630+
*errs = append(*errs, *wrappederr)
631+
return
632+
}
633+
f.Replace = append(f.Replace, replace)
634+
}
635+
}
636+
559637
// IsDirectoryPath reports whether the given path should be interpreted
560638
// as a directory path. Just like on the go command line, relative paths
561639
// and rooted paths are directory paths; the rest are module paths.
@@ -1165,6 +1243,10 @@ func (f *File) DropExclude(path, vers string) error {
11651243
}
11661244

11671245
func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
1246+
return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
1247+
}
1248+
1249+
func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
11681250
need := true
11691251
old := module.Version{Path: oldPath, Version: oldVers}
11701252
new := module.Version{Path: newPath, Version: newVers}
@@ -1178,12 +1260,12 @@ func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
11781260
}
11791261

11801262
var hint *Line
1181-
for _, r := range f.Replace {
1263+
for _, r := range *replace {
11821264
if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
11831265
if need {
11841266
// Found replacement for old; update to use new.
11851267
r.New = new
1186-
f.Syntax.updateLine(r.Syntax, tokens...)
1268+
syntax.updateLine(r.Syntax, tokens...)
11871269
need = false
11881270
continue
11891271
}
@@ -1196,7 +1278,7 @@ func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
11961278
}
11971279
}
11981280
if need {
1199-
f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
1281+
*replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
12001282
}
12011283
return nil
12021284
}
@@ -1282,49 +1364,55 @@ func (f *File) SortBlocks() {
12821364
// retract directives are not de-duplicated since comments are
12831365
// meaningful, and versions may be retracted multiple times.
12841366
func (f *File) removeDups() {
1367+
removeDups(f.Syntax, &f.Exclude, &f.Replace)
1368+
}
1369+
1370+
func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
12851371
kill := make(map[*Line]bool)
12861372

12871373
// Remove duplicate excludes.
1288-
haveExclude := make(map[module.Version]bool)
1289-
for _, x := range f.Exclude {
1290-
if haveExclude[x.Mod] {
1291-
kill[x.Syntax] = true
1292-
continue
1374+
if exclude != nil {
1375+
haveExclude := make(map[module.Version]bool)
1376+
for _, x := range *exclude {
1377+
if haveExclude[x.Mod] {
1378+
kill[x.Syntax] = true
1379+
continue
1380+
}
1381+
haveExclude[x.Mod] = true
12931382
}
1294-
haveExclude[x.Mod] = true
1295-
}
1296-
var excl []*Exclude
1297-
for _, x := range f.Exclude {
1298-
if !kill[x.Syntax] {
1299-
excl = append(excl, x)
1383+
var excl []*Exclude
1384+
for _, x := range *exclude {
1385+
if !kill[x.Syntax] {
1386+
excl = append(excl, x)
1387+
}
13001388
}
1389+
*exclude = excl
13011390
}
1302-
f.Exclude = excl
13031391

13041392
// Remove duplicate replacements.
13051393
// Later replacements take priority over earlier ones.
13061394
haveReplace := make(map[module.Version]bool)
1307-
for i := len(f.Replace) - 1; i >= 0; i-- {
1308-
x := f.Replace[i]
1395+
for i := len(*replace) - 1; i >= 0; i-- {
1396+
x := (*replace)[i]
13091397
if haveReplace[x.Old] {
13101398
kill[x.Syntax] = true
13111399
continue
13121400
}
13131401
haveReplace[x.Old] = true
13141402
}
13151403
var repl []*Replace
1316-
for _, x := range f.Replace {
1404+
for _, x := range *replace {
13171405
if !kill[x.Syntax] {
13181406
repl = append(repl, x)
13191407
}
13201408
}
1321-
f.Replace = repl
1409+
*replace = repl
13221410

13231411
// Duplicate require and retract directives are not removed.
13241412

13251413
// Drop killed statements from the syntax tree.
13261414
var stmts []Expr
1327-
for _, stmt := range f.Syntax.Stmt {
1415+
for _, stmt := range syntax.Stmt {
13281416
switch stmt := stmt.(type) {
13291417
case *Line:
13301418
if kill[stmt] {
@@ -1344,7 +1432,7 @@ func (f *File) removeDups() {
13441432
}
13451433
stmts = append(stmts, stmt)
13461434
}
1347-
f.Syntax.Stmt = stmts
1435+
syntax.Stmt = stmts
13481436
}
13491437

13501438
// lineLess returns whether li should be sorted before lj. It sorts

modfile/testdata/work/comment.golden

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// comment
2+
directory x // eol
3+
4+
// mid comment
5+
6+
// comment 2
7+
// comment 2 line 2
8+
directory y // eoy
9+
10+
// comment 3

modfile/testdata/work/comment.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// comment
2+
directory "x" // eol
3+
// mid comment
4+
5+
// comment 2
6+
// comment 2 line 2
7+
directory "y" // eoy
8+
// comment 3
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
directory ../foo
2+
3+
directory (
4+
/bar
5+
6+
baz
7+
)

modfile/testdata/work/directory.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
directory "../foo"
2+
3+
directory (
4+
"/bar"
5+
6+
"baz"
7+
)

modfile/testdata/work/empty.golden

Whitespace-only changes.

modfile/testdata/work/empty.in

Whitespace-only changes.

modfile/testdata/work/replace.golden

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
directory abc
2+
3+
replace xyz v1.2.3 => /tmp/z
4+
5+
replace xyz v1.3.4 => my/xyz v1.3.4-me
6+
7+
replace (
8+
w v1.0.0 => "./a,"
9+
w v1.0.1 => "./a()"
10+
w v1.0.2 => "./a[]"
11+
w v1.0.3 => "./a{}"
12+
)

0 commit comments

Comments
 (0)