Skip to content

Commit

Permalink
modfile: Add support for tool lines
Browse files Browse the repository at this point in the history
Add new tool directive to go.mod parser and functions
to add and drop them.

For golang/go#48429

Change-Id: I37667a69ded9d59ea248ec48ad35c87592103218
Reviewed-on: https://go-review.googlesource.com/c/mod/+/508355
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Sam Thanawalla <samthanawalla@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
ConradIrwin authored and matloob committed Jul 18, 2024
1 parent 79169e9 commit b56a28f
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 6 deletions.
80 changes: 75 additions & 5 deletions modfile/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type File struct {
Exclude []*Exclude
Replace []*Replace
Retract []*Retract
Tool []*Tool

Syntax *FileSyntax
}
Expand Down Expand Up @@ -93,6 +94,12 @@ type Retract struct {
Syntax *Line
}

// A Tool is a single tool statement.
type Tool struct {
Path string
Syntax *Line
}

// A VersionInterval represents a range of versions with upper and lower bounds.
// Intervals are closed: both bounds are included. When Low is equal to High,
// the interval may refer to a single version ('v1.2.3') or an interval
Expand Down Expand Up @@ -297,7 +304,7 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parse
})
}
continue
case "module", "godebug", "require", "exclude", "replace", "retract":
case "module", "godebug", "require", "exclude", "replace", "retract", "tool":
for _, l := range x.Line {
f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)
}
Expand Down Expand Up @@ -509,6 +516,21 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
Syntax: line,
}
f.Retract = append(f.Retract, retract)

case "tool":
if len(args) != 1 {
errorf("tool directive expects exactly one argument")
return
}
s, err := parseString(&args[0])
if err != nil {
errorf("invalid quoted string: %v", err)
return
}
f.Tool = append(f.Tool, &Tool{
Path: s,
Syntax: line,
})
}
}

Expand Down Expand Up @@ -1567,6 +1589,36 @@ func (f *File) DropRetract(vi VersionInterval) error {
return nil
}

// AddTool adds a new tool directive with the given path.
// It does nothing if the tool line already exists.
func (f *File) AddTool(path string) error {
for _, t := range f.Tool {
if t.Path == path {
return nil
}
}

f.Tool = append(f.Tool, &Tool{
Path: path,
Syntax: f.Syntax.addLine(nil, "tool", path),
})

f.SortBlocks()
return nil
}

// RemoveTool removes a tool directive with the given path.
// It does nothing if no such tool directive exists.
func (f *File) DropTool(path string) error {
for _, t := range f.Tool {
if t.Path == path {
t.Syntax.markRemoved()
*t = Tool{}
}
}
return nil
}

func (f *File) SortBlocks() {
f.removeDups() // otherwise sorting is unsafe

Expand All @@ -1593,9 +1645,9 @@ func (f *File) SortBlocks() {
}
}

// removeDups removes duplicate exclude and replace directives.
// removeDups removes duplicate exclude, replace and tool directives.
//
// Earlier exclude directives take priority.
// Earlier exclude and tool directives take priority.
//
// Later replace directives take priority.
//
Expand All @@ -1605,10 +1657,10 @@ func (f *File) SortBlocks() {
// retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times.
func (f *File) removeDups() {
removeDups(f.Syntax, &f.Exclude, &f.Replace)
removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool)
}

func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool) {
kill := make(map[*Line]bool)

// Remove duplicate excludes.
Expand Down Expand Up @@ -1649,6 +1701,24 @@ func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
}
*replace = repl

if tool != nil {
haveTool := make(map[string]bool)
for _, t := range *tool {
if haveTool[t.Path] {
kill[t.Syntax] = true
continue
}
haveTool[t.Path] = true
}
var newTool []*Tool
for _, t := range *tool {
if !kill[t.Syntax] {
newTool = append(newTool, t)
}
}
*tool = newTool
}

// Duplicate require and retract directives are not removed.

// Drop killed statements from the syntax tree.
Expand Down
129 changes: 129 additions & 0 deletions modfile/rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,72 @@ var dropGodebugTests = []struct {
},
}

var addToolTests = []struct {
desc, in, path, want string
}{
{
`add_first`,
`module example.com/m`,
`example.com/tool/v1`,
`module example.com/m
tool example.com/tool/v1`,
},
{
`sorted_correctly`,
`module example.com/m
tool example.com/tool2
`,
`example.com/tool1`,
`module example.com/m
tool (
example.com/tool1
example.com/tool2
)`,
},
{
`duplicates_ignored`,
`module example.com/m
tool example.com/tool1
`,
`example.com/tool1`,
`module example.com/m
tool example.com/tool1`,
},
}

var dropToolTests = []struct {
desc, in, path, want string
}{
{
`only`,
`module example.com/m
tool example.com/tool1`,
`example.com/tool1`,
`module example.com/m`,
},
{
`parenthesized`,
`module example.com/m
tool (
example.com/tool1
example.com/tool2
)`,
`example.com/tool1`,
`module example.com/m
tool example.com/tool2`,
},
{
`missing`,
`module example.com/m
tool (
example.com/tool2
)`,
`example.com/tool1`,
`module example.com/m
tool example.com/tool2`,
},
}

func fixV(path, version string) (string, error) {
if path != "example.com/m" {
return "", fmt.Errorf("module path must be example.com/m")
Expand Down Expand Up @@ -2051,6 +2117,7 @@ func TestAddOnEmptyFile(t *testing.T) {
t.Fatal(err)
}
got, err := f.Format()

if err != nil {
t.Fatal(err)
}
Expand All @@ -2061,3 +2128,65 @@ func TestAddOnEmptyFile(t *testing.T) {
})
}
}

func TestAddTool(t *testing.T) {
for _, tt := range addToolTests {
t.Run(tt.desc, func(t *testing.T) {
inFile, err := Parse("in", []byte(tt.in), nil)
if err != nil {
t.Fatal(err)
}
if err := inFile.AddTool(tt.path); err != nil {
t.Fatal(err)
}
inFile.Cleanup()
got, err := inFile.Format()
if err != nil {
t.Fatal(err)
}

outFile, err := Parse("out", []byte(tt.want), nil)
if err != nil {
t.Fatal(err)
}
want, err := outFile.Format()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, want) {
t.Fatalf("got:\n%s\nwant:\n%s", got, want)
}
})
}
}

func TestDropTool(t *testing.T) {
for _, tt := range dropToolTests {
t.Run(tt.desc, func(t *testing.T) {
inFile, err := Parse("in", []byte(tt.in), nil)
if err != nil {
t.Fatal(err)
}
if err := inFile.DropTool(tt.path); err != nil {
t.Fatal(err)
}
inFile.Cleanup()
got, err := inFile.Format()
if err != nil {
t.Fatal(err)
}

outFile, err := Parse("out", []byte(tt.want), nil)
if err != nil {
t.Fatal(err)
}
want, err := outFile.Format()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, want) {
t.Fatalf("got:\n%s\nwant:\n%s", got, want)
}
})
}
}
2 changes: 1 addition & 1 deletion modfile/work.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,5 +331,5 @@ func (f *WorkFile) SortBlocks() {
// retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times.
func (f *WorkFile) removeDups() {
removeDups(f.Syntax, nil, &f.Replace)
removeDups(f.Syntax, nil, &f.Replace, nil)
}

0 comments on commit b56a28f

Please sign in to comment.