From 4c7a8d63e4042d16f87bf63720e3814683b0cb4b Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 8 Oct 2019 23:06:30 +0200 Subject: [PATCH] cmd/go/internal/modfile: remove preceding empty lines when setting require When rewriting a go.mod file, we currently sort all of the require lines in a block. The way the parser works is that it considers preceding blank lines to be empty comment lines, and preceding empty comment lines are "owned" by their adjoining line. So when we go to sort them, the empty lines follow around each sorted entry, which doesn't make a whole lot of sense, since usually vertical space is inserted to show sections, and if things get moved around by sorting, those sections are no longer meaningful. This all results in one especially troublesome edge case: blank lines between a block opening ("require (") and the first block line ("golang.org/x/sys ...") are not treated the same way and are rewritten out of existence. Here's an example of the behavior this fixes. Starting input file: require ( golang.zx2c4.com/wireguard master golang.org/x/crypto latest golang.org/x/net latest golang.org/x/sys latest golang.org/x/text latest github.com/lxn/walk latest github.com/lxn/win latest ) Now we run this through `GOPROXY=direct go get -d`: require ( github.com/lxn/walk v0.0.0-20190619151032-86d8802c197a github.com/lxn/win v0.0.0-20190716185335-d1d36f0e4f48 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 golang.zx2c4.com/wireguard v0.0.20190806-0.20190822065259-3cedc22d7b49 ) Notice how the blank lines before lxn/walk and x/crypto were preserved. Finally, we have this be rewritten yet again with a call to `go build`: require ( github.com/lxn/walk v0.0.0-20190619151032-86d8802c197a github.com/lxn/win v0.0.0-20190716185335-d1d36f0e4f48 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 golang.zx2c4.com/wireguard v0.0.20190806-0.20190822065259-3cedc22d7b49 ) In this final resting point, the first blank line has been removed. The discrepancy between those two last stages are especially bothersome, because it makes for lots of dirty git commits and file contents bouncing back and forth. This commit fixes the problem as mentioned above, getting rid of those preceding blank lines. The output in all cases looks as it should, like this: require ( github.com/lxn/walk v0.0.0-20190619151032-86d8802c197a github.com/lxn/win v0.0.0-20190716185335-d1d36f0e4f48 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 golang.zx2c4.com/wireguard v0.0.20190806-0.20190822065259-3cedc22d7b49 ) Fixes #33779 Change-Id: I11c894440bd35f343ee62db3e06a50fa871f2599 Reviewed-on: https://go-review.googlesource.com/c/go/+/199917 Run-TryBot: Jason A. Donenfeld TryBot-Result: Gobot Gobot Reviewed-by: Bryan C. Mills --- src/cmd/go/internal/modfile/rule.go | 3 + src/cmd/go/internal/modfile/rule_test.go | 73 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/cmd/go/internal/modfile/rule.go b/src/cmd/go/internal/modfile/rule.go index e1f2687840..1b64216cff 100644 --- a/src/cmd/go/internal/modfile/rule.go +++ b/src/cmd/go/internal/modfile/rule.go @@ -566,6 +566,9 @@ func (f *File) SetRequire(req []*Require) { var newLines []*Line for _, line := range stmt.Line { if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" { + if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 { + line.Comments.Before = line.Comments.Before[:0] + } line.Token[1] = need[p] delete(need, p) setIndirect(line, indirect[p]) diff --git a/src/cmd/go/internal/modfile/rule_test.go b/src/cmd/go/internal/modfile/rule_test.go index b88ad62916..edd289053b 100644 --- a/src/cmd/go/internal/modfile/rule_test.go +++ b/src/cmd/go/internal/modfile/rule_test.go @@ -8,6 +8,8 @@ import ( "bytes" "fmt" "testing" + + "cmd/go/internal/module" ) var addRequireTests = []struct { @@ -59,6 +61,40 @@ var addRequireTests = []struct { }, } +var setRequireTests = []struct { + in string + mods []struct { + path string + vers string + } + out string +}{ + { + `module m + require ( + x.y/b v1.2.3 + + x.y/a v1.2.3 + ) + `, + []struct { + path string + vers string + }{ + {"x.y/a", "v1.2.3"}, + {"x.y/b", "v1.2.3"}, + {"x.y/c", "v1.2.3"}, + }, + `module m + require ( + x.y/a v1.2.3 + x.y/b v1.2.3 + x.y/c v1.2.3 + ) + `, + }, +} + func TestAddRequire(t *testing.T) { for i, tt := range addRequireTests { t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { @@ -88,3 +124,40 @@ func TestAddRequire(t *testing.T) { }) } } + +func TestSetRequire(t *testing.T) { + for i, tt := range setRequireTests { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + f, err := Parse("in", []byte(tt.in), nil) + if err != nil { + t.Fatal(err) + } + g, err := Parse("out", []byte(tt.out), nil) + if err != nil { + t.Fatal(err) + } + golden, err := g.Format() + if err != nil { + t.Fatal(err) + } + var mods []*Require + for _, mod := range tt.mods { + mods = append(mods, &Require{ + Mod: module.Version{ + Path: mod.path, + Version: mod.vers, + }, + }) + } + + f.SetRequire(mods) + out, err := f.Format() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(out, golden) { + t.Errorf("have:\n%s\nwant:\n%s", out, golden) + } + }) + } +} -- 2.50.0