From 79169e9d3645b69457c3c546644e29ba620146e7 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 16 Jul 2024 11:35:21 -0400 Subject: [PATCH 1/4] LICENSE: update per Google Legal Very minor tweaks: - Remove (c) pseudosymbol. - Remove "All Rights Reserved." - Change "Google Inc." (no longer exists) to "Google LLC". [git-generate] echo ' ,s/\(c\) // ,s/ All rights reserved.// ,s/Google Inc./Google LLC/ w q ' | sam -d LICENSE Change-Id: Icf1e124b083e2cfd5c804ccef7f563881d5f41d9 Reviewed-on: https://go-review.googlesource.com/c/mod/+/598578 LUCI-TryBot-Result: Go LUCI Auto-Submit: Russ Cox Reviewed-by: Ian Lance Taylor --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 6a66aea..2a7cf70 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From b56a28f8bd8379d47ee77b658799ce29061f6abe Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 21 Jun 2023 13:00:28 -0600 Subject: [PATCH 2/4] modfile: Add support for tool lines 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 Reviewed-by: Sam Thanawalla LUCI-TryBot-Result: Go LUCI --- modfile/rule.go | 80 +++++++++++++++++++++++++-- modfile/rule_test.go | 129 +++++++++++++++++++++++++++++++++++++++++++ modfile/work.go | 2 +- 3 files changed, 205 insertions(+), 6 deletions(-) diff --git a/modfile/rule.go b/modfile/rule.go index 66dcaf9..3e4a1d0 100644 --- a/modfile/rule.go +++ b/modfile/rule.go @@ -43,6 +43,7 @@ type File struct { Exclude []*Exclude Replace []*Replace Retract []*Retract + Tool []*Tool Syntax *FileSyntax } @@ -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 @@ -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) } @@ -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, + }) } } @@ -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 @@ -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. // @@ -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. @@ -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. diff --git a/modfile/rule_test.go b/modfile/rule_test.go index 4d0d12a..c75a77a 100644 --- a/modfile/rule_test.go +++ b/modfile/rule_test.go @@ -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") @@ -2051,6 +2117,7 @@ func TestAddOnEmptyFile(t *testing.T) { t.Fatal(err) } got, err := f.Format() + if err != nil { t.Fatal(err) } @@ -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) + } + }) + } +} diff --git a/modfile/work.go b/modfile/work.go index 8f54897..5387d0c 100644 --- a/modfile/work.go +++ b/modfile/work.go @@ -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) } From d1f873e3c1b2cf7231a30697aea158ae6cfdbb5f Mon Sep 17 00:00:00 2001 From: James Hartig Date: Fri, 19 Mar 2021 15:24:42 -0400 Subject: [PATCH 3/4] modfile: fix Cleanup clobbering Line reference Fixes golang/go#45130 Change-Id: I2dccba5e958911177f10a5104a182f86ff8378d9 Reviewed-on: https://go-review.googlesource.com/c/mod/+/303234 Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase --- modfile/read.go | 7 ++- modfile/read_test.go | 131 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 3 deletions(-) diff --git a/modfile/read.go b/modfile/read.go index 2205682..de1b982 100644 --- a/modfile/read.go +++ b/modfile/read.go @@ -226,8 +226,9 @@ func (x *FileSyntax) Cleanup() { continue } if ww == 1 && len(stmt.RParen.Comments.Before) == 0 { - // Collapse block into single line. - line := &Line{ + // Collapse block into single line but keep the Line reference used by the + // parsed File structure. + *stmt.Line[0] = Line{ Comments: Comments{ Before: commentsAdd(stmt.Before, stmt.Line[0].Before), Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix), @@ -235,7 +236,7 @@ func (x *FileSyntax) Cleanup() { }, Token: stringsAdd(stmt.Token, stmt.Line[0].Token), } - x.Stmt[w] = line + x.Stmt[w] = stmt.Line[0] w++ continue } diff --git a/modfile/read_test.go b/modfile/read_test.go index efc75e1..8f17eea 100644 --- a/modfile/read_test.go +++ b/modfile/read_test.go @@ -603,3 +603,134 @@ comments before "// k" }) } } + +func TestCleanup(t *testing.T) { + for _, test := range []struct { + desc string + want string + input []Expr + }{ + { + desc: "simple_lines", + want: `line: module a +line: require b v1.0.0 +`, + input: []Expr{ + &Line{ + Token: []string{"module", "a"}, + }, + &Line{ + Token: []string{"require", "b", "v1.0.0"}, + }, + &Line{ + Token: nil, + }, + }, + }, { + desc: "line_block", + want: `line: module a +block: require +blockline: b v1.0.0 +blockline: c v1.0.0 +`, + input: []Expr{ + &Line{ + Token: []string{"module", "a"}, + }, + &LineBlock{ + Token: []string{"require"}, + Line: []*Line{ + { + Token: []string{"b", "v1.0.0"}, + InBlock: true, + }, + { + Token: nil, + InBlock: true, + }, + { + Token: []string{"c", "v1.0.0"}, + InBlock: true, + }, + }, + }, + }, + }, { + desc: "collapse", + want: `line: module a +line: require b v1.0.0 +`, + input: []Expr{ + &Line{ + Token: []string{"module", "a"}, + }, + &LineBlock{ + Token: []string{"require"}, + Line: []*Line{ + { + Token: []string{"b", "v1.0.0"}, + InBlock: true, + }, + { + Token: nil, + InBlock: true, + }, + }, + }, + }, + }, + } { + t.Run(test.desc, func(t *testing.T) { + syntax := &FileSyntax{ + Stmt: test.input, + } + syntax.Cleanup() + + buf := &bytes.Buffer{} + for _, stmt := range syntax.Stmt { + switch stmt := stmt.(type) { + case *Line: + fmt.Fprintf(buf, "line: %v\n", strings.Join(stmt.Token, " ")) + case *LineBlock: + fmt.Fprintf(buf, "block: %v\n", strings.Join(stmt.Token, " ")) + for _, line := range stmt.Line { + fmt.Fprintf(buf, "blockline: %v\n", strings.Join(line.Token, " ")) + } + } + } + + got := strings.TrimSpace(buf.String()) + want := strings.TrimSpace(test.want) + if got != want { + t.Errorf("got:\n%s\nwant:\n%s", got, want) + } + }) + } +} + +// Issue 45130: File.Cleanup breaks references so future edits do nothing +func TestCleanupMaintainsRefs(t *testing.T) { + lineB := &Line{ + Token: []string{"b", "v1.0.0"}, + InBlock: true, + } + syntax := &FileSyntax{ + Stmt: []Expr{ + &LineBlock{ + Token: []string{"require"}, + Line: []*Line{ + lineB, + { + Token: nil, + InBlock: true, + }, + }, + }, + }, + } + syntax.Cleanup() + + if syntax.Stmt[0] != lineB { + t.Errorf("got:\n%v\nwant:\n%v", syntax.Stmt[0], lineB) + } +} From bc151c4e8ccc31931553c47d43e41c0efc246096 Mon Sep 17 00:00:00 2001 From: Mike Seplowitz Date: Mon, 29 Jul 2024 10:02:17 -0400 Subject: [PATCH 4/4] README: fix link to x/tools Change-Id: I4803bfe7da3b21fdfe503b9804015f0a5104a52e Reviewed-on: https://go-review.googlesource.com/c/mod/+/601441 Reviewed-by: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI Auto-Submit: Ian Lance Taylor Reviewed-by: Michael Knyszek --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94da72d..4bbeeaa 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ That is, it is for direct manipulation of Go modules themselves. It is NOT about supporting general development tools that need to do things like load packages in module mode. That use case, where modules are incidental rather than the focus, -should remain in [x/tools](https://pkg.go.dev/golang/org/x/tools), +should remain in [x/tools](https://pkg.go.dev/golang.org/x/tools), specifically [x/tools/go/packages](https://pkg.go.dev/golang.org/x/tools/go/packages). The specific case of loading packages should still be done by