[tools] gopls/internal: add code action "move to a new file"

24 views
Skip to first unread message

Gerrit Bot (Gerrit)

unread,
Feb 21, 2024, 8:34:15 PM2/21/24
to goph...@pubsubhelper.golang.org, Chiawen Chen, golang-co...@googlegroups.com

Gerrit Bot has uploaded the change for review

Commit message

gopls/internal: add code action "move to a new file"

This code action moves selected code sections to a newly created file within the same package. The created filename is chosen as the first {function, type, const, var} name encountered. In addition, import declarations are added or removed as needed.

Fixes golang/go#65707
Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
GitHub-Last-Rev: 8c85d4325defb60788953717355576bed48ca31f
GitHub-Pull-Request: golang/tools#479

Change diff

diff --git a/gopls/internal/cache/snapshot.go b/gopls/internal/cache/snapshot.go
index d81fd20..1725cea 100644
--- a/gopls/internal/cache/snapshot.go
+++ b/gopls/internal/cache/snapshot.go
@@ -1658,6 +1658,22 @@
return found && strings.Contains(after, "/")
}

+// Sandbox clones the reciever, applying given file modfications as overlays.
+func (s *Snapshot) Sandbox(ctx, bgCtx context.Context, modifications []file.Modification) *Snapshot {
+ updatedFiles := make(map[protocol.DocumentURI]file.Handle)
+ for _, m := range modifications {
+ updatedFiles[m.URI] = &overlay{
+ uri: m.URI,
+ content: m.Text,
+ }
+ }
+ cloned, _ := s.clone(ctx, bgCtx, StateChange{
+ Modifications: modifications,
+ Files: updatedFiles,
+ }, func() {})
+ return cloned
+}
+
// clone copies state from the receiver into a new Snapshot, applying the given
// state changes.
//
diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go
index df4ca51..6fdd118 100644
--- a/gopls/internal/golang/codeaction.go
+++ b/gopls/internal/golang/codeaction.go
@@ -86,6 +86,11 @@
return nil, err
}
actions = append(actions, extractions...)
+ moves, err := getMoveToNewFileCodeAction(pgf, rng, snapshot.Options())
+ if err != nil {
+ return nil, err
+ }
+ actions = append(actions, moves)
}
}

diff --git a/gopls/internal/golang/format.go b/gopls/internal/golang/format.go
index 3e5668b..1db320f 100644
--- a/gopls/internal/golang/format.go
+++ b/gopls/internal/golang/format.go
@@ -92,6 +92,38 @@
return computeTextEdits(ctx, pgf, formatted)
}

+func formatImportsBytes(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]byte, error) {
+ _, done := event.Start(ctx, "golang.formatImportsBytes")
+ defer done()
+
+ errorPrefix := "formatImportsBytes"
+
+ text, err := fh.Content()
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ var out []byte
+ if err := snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, opts *imports.Options) error {
+ fixes, err := imports.FixImports(ctx, fh.URI().Path(), text, opts)
+ if err != nil {
+ return fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+ out, err = imports.ApplyFixes(fixes, fh.URI().Path(), text, opts, parsego.Full)
+ if err != nil {
+ return fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+ return nil
+ }); err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+ out, err = format.Source(out)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+ return out, nil
+}
+
func formatSource(ctx context.Context, fh file.Handle) ([]byte, error) {
_, done := event.Start(ctx, "golang.formatSource")
defer done()
diff --git a/gopls/internal/golang/move_to_new_file.go b/gopls/internal/golang/move_to_new_file.go
new file mode 100644
index 0000000..4f0d40e
--- /dev/null
+++ b/gopls/internal/golang/move_to_new_file.go
@@ -0,0 +1,326 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package golang
+
+// This file defines the code action "move to a new file".
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/tools/gopls/internal/cache"
+ "golang.org/x/tools/gopls/internal/cache/parsego"
+ "golang.org/x/tools/gopls/internal/file"
+ "golang.org/x/tools/gopls/internal/protocol"
+ "golang.org/x/tools/gopls/internal/protocol/command"
+ "golang.org/x/tools/gopls/internal/settings"
+)
+
+func getMoveToNewFileCodeAction(pgf *parsego.File, rng protocol.Range, _ *settings.Options) (protocol.CodeAction, error) {
+ ok := canMoveToANewFile(pgf, rng)
+ if !ok {
+ return protocol.CodeAction{}, nil
+ }
+ cmd, err := command.NewMoveToANewFileCommand("m", command.MoveToANewFileArgs{URI: pgf.URI, Range: rng})
+ if err != nil {
+ return protocol.CodeAction{}, err
+ }
+ return protocol.CodeAction{
+ Title: "Move to a new file",
+ Kind: protocol.RefactorExtract,
+ Command: &cmd,
+ }, nil
+}
+
+// canMoveToANewFile reports whether the code in the given range can be move to a new file.
+func canMoveToANewFile(pgf *parsego.File, rng protocol.Range) bool {
+ _, err := moveToANewFileInternal(context.Background(), nil, nil, pgf, rng, true)
+ if err != nil {
+ return false
+ } else {
+ return true
+ }
+}
+
+// MoveToANewFile moves selected declarations into a new file.
+func MoveToANewFile(
+ ctx context.Context,
+ snapshot *cache.Snapshot,
+ fh file.Handle,
+ rng protocol.Range,
+) (*protocol.WorkspaceEdit, error) {
+ pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
+ if err != nil {
+ return nil, fmt.Errorf("MoveToANewFile: %w", err)
+ }
+ return moveToANewFileInternal(ctx, snapshot, fh, pgf, rng, false)
+}
+
+// moveToANewFileInternal moves selected declarations into a new file.
+func moveToANewFileInternal(
+ ctx context.Context,
+ snapshot *cache.Snapshot,
+ fh file.Handle,
+ pgf *parsego.File,
+ rng protocol.Range,
+ dry bool,
+) (*protocol.WorkspaceEdit, error) {
+ // todo: check client allows create file
+ errorPrefix := "moveToANewFileInternal"
+
+ start, end, err := pgf.RangePos(rng)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ start, end, filename, err := findRangeAndFilename(pgf, start, end)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ if dry {
+ return nil, nil
+ }
+
+ start, end = adjustRangeForEmptyLines(pgf, start, end)
+
+ createFileURI, err := resolveCreateFileURI(pgf.URI.Dir().Path(), filename)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ replaceRange, err := pgf.PosRange(start, end)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ start -= pgf.File.FileStart
+ end -= pgf.File.FileStart
+
+ modifiedText := make([]byte, 0)
+ modifiedText = append(modifiedText, pgf.Src[:start]...)
+ modifiedText = append(modifiedText, pgf.Src[end:]...)
+
+ packageName := pgf.File.Name.Name
+ createFileTextWithoutImports := []byte("package " + packageName + "\n" + string(pgf.Src[start:end]))
+
+ modifications := []file.Modification{
+ {
+ URI: fh.URI(),
+ Action: file.Change,
+ Text: modifiedText,
+ },
+ {
+ URI: createFileURI,
+ Action: file.Change,
+ Text: createFileTextWithoutImports,
+ },
+ }
+
+ // apply edits into a cloned snapshot and calculate import edits
+ snapshot = snapshot.Sandbox(ctx, ctx, modifications)
+ newFh, err := snapshot.ReadFile(ctx, fh.URI())
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ newPgf, err := snapshot.ParseGo(ctx, newFh, parsego.Full)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ importEdits, _, err := allImportsFixes(ctx, snapshot, newPgf)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ createFh, err := snapshot.ReadFile(ctx, createFileURI)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ createFileText, err := formatImportsBytes(ctx, snapshot, createFh)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errorPrefix, err)
+ }
+
+ return &protocol.WorkspaceEdit{
+ DocumentChanges: []protocol.DocumentChanges{
+ {
+ // original file edits
+ TextDocumentEdit: &protocol.TextDocumentEdit{
+ TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
+ TextDocumentIdentifier: protocol.TextDocumentIdentifier{
+ URI: fh.URI(),
+ },
+ Version: fh.Version(),
+ },
+ Edits: protocol.AsAnnotatedTextEdits(
+ append(
+ importEdits,
+ protocol.TextEdit{
+ Range: replaceRange,
+ NewText: "",
+ }),
+ ),
+ },
+ },
+ {
+ CreateFile: &protocol.CreateFile{
+ Kind: "create",
+ URI: createFileURI,
+ },
+ },
+ {
+ // created file edits
+ TextDocumentEdit: &protocol.TextDocumentEdit{
+ TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
+ TextDocumentIdentifier: protocol.TextDocumentIdentifier{
+ URI: createFileURI,
+ },
+ Version: 0,
+ },
+ Edits: protocol.AsAnnotatedTextEdits([]protocol.TextEdit{
+ {
+ Range: protocol.Range{},
+ NewText: string(createFileText),
+ },
+ })},
+ },
+ },
+ }, nil
+}
+
+// resolveCreateFileURI checks that basename.go does not exists in dir, otherwise
+// select basename.{1,2,3,4,5}.go as filename.
+func resolveCreateFileURI(dir string, basename string) (protocol.DocumentURI, error) {
+ basename = strings.ToLower(basename)
+ newPath := filepath.Join(dir, basename+".go")
+ for count := 1; ; count++ {
+ if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
+ break
+ }
+ if count >= 5 {
+ return "", fmt.Errorf("resolveNewFileURI: exceeded retry limit")
+ }
+ filename := fmt.Sprintf("%s.%d.go", basename, count)
+ newPath = filepath.Join(dir, filename)
+ }
+ return protocol.URIFromPath(newPath), nil
+}
+
+// findRangeAndFilename checks the selection is valid and extends range as needed and returns adjusted
+// range and selected filename.
+func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
+ if intersect(start, end, pgf.File.Package, pgf.File.Name.End()) {
+ return 0, 0, "", errors.New("selection cannot intersect a package declaration")
+ }
+ firstName := ""
+ for _, node := range pgf.File.Decls {
+ if intersect(start, end, node.Pos(), node.End()) {
+ if v, ok := node.(*ast.GenDecl); ok && v.Tok == token.IMPORT {
+ return 0, 0, "", errors.New("selection cannot intersect an import declaration")
+ }
+ if _, ok := node.(*ast.BadDecl); ok {
+ return 0, 0, "", errors.New("selection cannot intersect a bad declaration")
+ }
+ // should work when only selecting keyword "func" or function name
+ if v, ok := node.(*ast.FuncDecl); ok && contain(v.Pos(), v.Name.End(), start, end) {
+ start, end = v.Pos(), v.End()
+ }
+ // should work when only selecting keyword "type", "var", "const"
+ if v, ok := node.(*ast.GenDecl); ok && (v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
+ v.Tok == token.CONST && contain(v.Pos(), v.Pos()+5, start, end) ||
+ v.Tok == token.VAR && contain(v.Pos(), v.Pos()+3, start, end)) {
+ start, end = v.Pos(), v.End()
+ }
+ if !contain(start, end, node.Pos(), node.End()) {
+ return 0, 0, "", errors.New("selection cannot partially intersect a node")
+ } else {
+ if firstName == "" {
+ firstName = getNodeName(node)
+ }
+ // extends selection to docs comments
+ if c := getCommentGroup(node); c != nil {
+ if c.Pos() < start {
+ start = c.Pos()
+ }
+ }
+ }
+ }
+ }
+ for _, node := range pgf.File.Comments {
+ if intersect(start, end, node.Pos(), node.End()) {
+ if !contain(start, end, node.Pos(), node.End()) {
+ return 0, 0, "", errors.New("selection cannot partially intersect a comment")
+ }
+ }
+ }
+ if firstName == "" {
+ return 0, 0, "", errors.New("nothing selected")
+ }
+ return start, end, firstName, nil
+}
+
+func adjustRangeForEmptyLines(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos) {
+ i := int(end)
+ for ; i-int(pgf.File.FileStart) < len(pgf.Src); i++ {
+ c := pgf.Src[i-int(pgf.File.FileStart)]
+ if c == ' ' || c == '\t' || c == '\n' {
+ continue
+ } else {
+ break
+ }
+ }
+ return start, token.Pos(i)
+}
+
+func getCommentGroup(node ast.Node) *ast.CommentGroup {
+ switch n := node.(type) {
+ case *ast.GenDecl:
+ return n.Doc
+ case *ast.FuncDecl:
+ return n.Doc
+ }
+ return nil
+}
+
+// getNodeName returns the first func name or variable name
+func getNodeName(node ast.Node) string {
+ switch n := node.(type) {
+ case *ast.FuncDecl:
+ return n.Name.Name
+ case *ast.GenDecl:
+ if len(n.Specs) == 0 {
+ return ""
+ }
+ switch m := n.Specs[0].(type) {
+ case *ast.TypeSpec:
+ return m.Name.Name
+ case *ast.ValueSpec:
+ if len(m.Names) == 0 {
+ return ""
+ }
+ return m.Names[0].Name
+ }
+ }
+ return ""
+}
+
+// intersect checks if [a, b) and [c, d) intersect, assumming a <= b and c <= d
+func intersect(a, b, c, d token.Pos) bool {
+ return !(b <= c || d <= a)
+}
+
+// contain checks if [a, b) contains [c, d), assumming a <= b and c <= d
+func contain(a, b, c, d token.Pos) bool {
+ return a <= c && d <= b
+}
diff --git a/gopls/internal/protocol/command/command_gen.go b/gopls/internal/protocol/command/command_gen.go
index 2ee0a5d..2195abf 100644
--- a/gopls/internal/protocol/command/command_gen.go
+++ b/gopls/internal/protocol/command/command_gen.go
@@ -38,6 +38,7 @@
ListKnownPackages Command = "list_known_packages"
MaybePromptForTelemetry Command = "maybe_prompt_for_telemetry"
MemStats Command = "mem_stats"
+ MoveToANewFile Command = "move_to_a_new_file"
RegenerateCgo Command = "regenerate_cgo"
RemoveDependency Command = "remove_dependency"
ResetGoModDiagnostics Command = "reset_go_mod_diagnostics"
@@ -74,6 +75,7 @@
ListKnownPackages,
MaybePromptForTelemetry,
MemStats,
+ MoveToANewFile,
RegenerateCgo,
RemoveDependency,
ResetGoModDiagnostics,
@@ -183,6 +185,12 @@
return nil, s.MaybePromptForTelemetry(ctx)
case "gopls.mem_stats":
return s.MemStats(ctx)
+ case "gopls.move_to_a_new_file":
+ var a0 MoveToANewFileArgs
+ if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
+ return nil, err
+ }
+ return nil, s.MoveToANewFile(ctx, a0)
case "gopls.regenerate_cgo":
var a0 URIArg
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
@@ -475,6 +483,18 @@
}, nil
}

+func NewMoveToANewFileCommand(title string, a0 MoveToANewFileArgs) (protocol.Command, error) {
+ args, err := MarshalArgs(a0)
+ if err != nil {
+ return protocol.Command{}, err
+ }
+ return protocol.Command{
+ Title: title,
+ Command: "gopls.move_to_a_new_file",
+ Arguments: args,
+ }, nil
+}
+
func NewRegenerateCgoCommand(title string, a0 URIArg) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
diff --git a/gopls/internal/protocol/command/interface.go b/gopls/internal/protocol/command/interface.go
index b0dd060..aeda6aa 100644
--- a/gopls/internal/protocol/command/interface.go
+++ b/gopls/internal/protocol/command/interface.go
@@ -148,6 +148,11 @@
// themselves.
AddImport(context.Context, AddImportArgs) error

+ // MoveToANewFile: Move selected codes to a new file
+ //
+ // Used by the code action of the same name.
+ MoveToANewFile(context.Context, MoveToANewFileArgs) error
+
// StartDebugging: Start the gopls debug server
//
// Start the gopls debug server if it isn't running, and return the debug
@@ -330,6 +335,12 @@
URI protocol.DocumentURI
}

+type MoveToANewFileArgs struct {
+ // URI of the file
+ URI protocol.DocumentURI
+ Range protocol.Range
+}
+
type ListKnownPackagesResult struct {
// Packages is a list of packages relative
// to the URIArg passed by the command request.
diff --git a/gopls/internal/protocol/tsdocument_changes.go b/gopls/internal/protocol/tsdocument_changes.go
index 2c7a524..393c5c8 100644
--- a/gopls/internal/protocol/tsdocument_changes.go
+++ b/gopls/internal/protocol/tsdocument_changes.go
@@ -6,14 +6,16 @@

import (
"encoding/json"
+ "errors"
"fmt"
)

-// DocumentChanges is a union of a file edit and directory rename operations
+// DocumentChanges is a union of a file edit, file creation, and directory rename operations
// for package renaming feature. At most one field of this struct is non-nil.
type DocumentChanges struct {
TextDocumentEdit *TextDocumentEdit
RenameFile *RenameFile
+ CreateFile *CreateFile
}

func (d *DocumentChanges) UnmarshalJSON(data []byte) error {
@@ -26,10 +28,16 @@
if _, ok := m["textDocument"]; ok {
d.TextDocumentEdit = new(TextDocumentEdit)
return json.Unmarshal(data, d.TextDocumentEdit)
+ } else if kind, ok := m["kind"]; ok {
+ if kind == "create" {
+ d.CreateFile = new(CreateFile)
+ return json.Unmarshal(data, d.CreateFile)
+ } else if kind == "rename" {
+ d.RenameFile = new(RenameFile)
+ return json.Unmarshal(data, d.RenameFile)
+ }
}
-
- d.RenameFile = new(RenameFile)
- return json.Unmarshal(data, d.RenameFile)
+ return errors.New("don't know how to unmarshal")
}

func (d *DocumentChanges) MarshalJSON() ([]byte, error) {
@@ -37,6 +45,8 @@
return json.Marshal(d.TextDocumentEdit)
} else if d.RenameFile != nil {
return json.Marshal(d.RenameFile)
+ } else if d.CreateFile != nil {
+ return json.Marshal(d.CreateFile)
}
- return nil, fmt.Errorf("Empty DocumentChanges union value")
+ return nil, fmt.Errorf("empty DocumentChanges union value")
}
diff --git a/gopls/internal/server/command.go b/gopls/internal/server/command.go
index 19ea884..039393b 100644
--- a/gopls/internal/server/command.go
+++ b/gopls/internal/server/command.go
@@ -870,6 +870,22 @@
})
}

+func (c *commandHandler) MoveToANewFile(ctx context.Context, args command.MoveToANewFileArgs) error {
+ return c.run(ctx, commandConfig{
+ progress: "Move to a new file",
+ forURI: args.URI,
+ }, func(ctx context.Context, deps commandDeps) error {
+ edit, err := golang.MoveToANewFile(ctx, deps.snapshot, deps.fh, args.Range)
+ if err != nil {
+ return err
+ }
+ if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{Edit: *edit}); err != nil {
+ return fmt.Errorf("could not apply edits: %v", err)
+ }
+ return nil
+ })
+}
+
func (c *commandHandler) StartDebugging(ctx context.Context, args command.DebuggingArgs) (result command.DebuggingResult, _ error) {
addr := args.Addr
if addr == "" {
diff --git a/gopls/internal/test/integration/fake/editor.go b/gopls/internal/test/integration/fake/editor.go
index e937764..8125f04 100644
--- a/gopls/internal/test/integration/fake/editor.go
+++ b/gopls/internal/test/integration/fake/editor.go
@@ -761,6 +761,18 @@
return e.setBufferContentLocked(ctx, path, true, patched, edits)
}

+// OffsetLocation returns the Location for integers offsets start and end
+func (e *Editor) OffsetLocation(bufName string, start, end int) (protocol.Location, error) {
+ e.mu.Lock()
+ buf, ok := e.buffers[bufName]
+ e.mu.Unlock()
+ if !ok {
+ return protocol.Location{}, ErrUnknownBuffer
+ }
+
+ return buf.mapper.OffsetLocation(start, end)
+}
+
// EditBuffer applies the given test edits to the buffer identified by path.
func (e *Editor) EditBuffer(ctx context.Context, path string, edits []protocol.TextEdit) error {
e.mu.Lock()
@@ -1402,7 +1414,11 @@
if change.TextDocumentEdit != nil {
return e.applyTextDocumentEdit(ctx, *change.TextDocumentEdit)
}
- panic("Internal error: one of RenameFile or TextDocumentEdit must be set")
+ if change.CreateFile != nil {
+ path := e.sandbox.Workdir.URIToPath(change.CreateFile.URI)
+ return e.sandbox.Workdir.WriteFile(ctx, path, "")
+ }
+ panic("Internal error: one of RenameFile, CreateFile, or TextDocumentEdit must be set")
}

func (e *Editor) applyTextDocumentEdit(ctx context.Context, change protocol.TextDocumentEdit) error {
diff --git a/gopls/internal/test/integration/misc/move_to_new_file_test.go b/gopls/internal/test/integration/misc/move_to_new_file_test.go
new file mode 100644
index 0000000..adf1ec8
--- /dev/null
+++ b/gopls/internal/test/integration/misc/move_to_new_file_test.go
@@ -0,0 +1,386 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package misc
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+ "testing"
+
+ . "golang.org/x/tools/gopls/internal/test/integration"
+
+ "golang.org/x/tools/gopls/internal/protocol"
+)
+
+func dedent(s string) string {
+ s = strings.TrimPrefix(s, "\n")
+ indents := regexp.MustCompile("^\t*").FindString(s)
+ return regexp.MustCompile(fmt.Sprintf("(?m)^\t{0,%d}", len(indents))).ReplaceAllString(s, "")
+}
+
+func indent(s string) string {
+ return regexp.MustCompile("(?m)^").ReplaceAllString(s, "\t")
+}
+
+// compileTemplate replaces two â–ˆ characters in text and write to dest and returns
+// the location enclosed by the two â–ˆ
+func compileTemplate(env *Env, text string, dest string) protocol.Location {
+ i := strings.Index(text, "â–ˆ")
+ j := strings.LastIndex(text, "â–ˆ")
+ if strings.Count(text, "â–ˆ") != 2 {
+ panic("expecting exactly two â–ˆ characters in source")
+ }
+ out := text[:i] + text[i+len("â–ˆ"):j] + text[j+len("â–ˆ"):]
+ env.Sandbox.Workdir.WriteFile(env.Ctx, dest, out)
+ env.OpenFile(dest)
+ loc, err := env.Editor.OffsetLocation(dest, i, j-len("â–ˆ"))
+ if err != nil {
+ panic(err)
+ }
+ return loc
+}
+
+func TestMoveToANewFile(t *testing.T) {
+ const files = `
+-- go.mod --
+module mod.com
+
+go 1.18
+-- main.go --
+package main
+
+-- filealreadexists.go --
+package main
+
+-- filealreadexistsmore.go --
+package main
+
+-- filealreadexistsmore.1.go --
+package main
+
+`
+ for _, tc := range []struct {
+ name string
+ source string
+ fixed string
+ createdFilename string
+ created string
+ }{
+ {
+ name: "func declaration",
+ source: `
+ package main
+
+ func _() {}
+
+ // fn docs
+ â–ˆfunc fn() {}â–ˆ
+ `,
+ fixed: `
+ package main
+
+ func _() {}
+
+ `,
+ createdFilename: "fn.go",
+ created: `
+ package main
+
+ // fn docs
+ func fn() {}
+ `,
+ },
+ {
+ name: "only select function name",
+ source: `
+ package main
+ func â–ˆFâ–ˆ() {}
+ `,
+ fixed: `
+ package main
+ `,
+ createdFilename: "f.go",
+ created: `
+ package main
+
+ func F() {}
+ `,
+ },
+ {
+ name: "type declaration",
+ source: `
+ package main
+
+ // T docs
+ â–ˆtype T int
+ type S intâ–ˆ
+ `,
+ fixed: `
+ package main
+
+ `,
+ createdFilename: "t.go",
+ created: `
+ package main
+
+ // T docs
+ type T int
+ type S int
+ `,
+ },
+ {
+ name: "const and var declaration",
+ source: `
+ package main
+
+ // c docs
+ â–ˆconst c = 0
+ var v = 0â–ˆ
+ `,
+ fixed: `
+ package main
+
+ `,
+ createdFilename: "c.go",
+ created: `
+ package main
+
+ // c docs
+ const c = 0
+
+ var v = 0
+ `,
+ },
+ {
+ name: "select only const keyword",
+ source: `
+ package main
+
+ â–ˆconstâ–ˆ (
+ A = iota
+ B
+ C
+ )
+ `,
+ fixed: `
+ package main
+
+ `,
+ createdFilename: "a.go",
+ created: `
+ package main
+
+ const (
+ A = iota
+ B
+ C
+ )
+ `,
+ },
+ {
+ name: "select surrounding comments",
+ source: `
+ package main
+
+ â–ˆ// above
+
+ func fn() {}
+
+ // belowâ–ˆ
+ `,
+ fixed: `
+ package main
+
+ `,
+ createdFilename: "fn.go",
+ created: `
+ package main
+
+ // above
+
+ func fn() {}
+
+ // below
+ `,
+ },
+
+ {
+ name: "create file name conflict",
+ source: `
+ package main
+ â–ˆfunc filealreadexists() {}â–ˆ
+ `,
+ fixed: `
+ package main
+ `,
+ createdFilename: "filealreadexists.1.go",
+ created: `
+ package main
+
+ func filealreadexists() {}
+ `,
+ },
+ {
+ name: "create file name conflict once more",
+ source: `
+ package main
+ â–ˆfunc filealreadexistsmore() {}â–ˆ
+ `,
+ fixed: `
+ package main
+ `,
+ createdFilename: "filealreadexistsmore.2.go",
+ created: `
+ package main
+
+ func filealreadexistsmore() {}
+ `,
+ },
+ {
+ name: "imports",
+ source: `
+ package main
+ import "fmt"
+ â–ˆfunc F() {
+ fmt.Println()
+ }â–ˆ
+ `,
+ fixed: `
+ package main
+ `,
+ createdFilename: "f.go",
+ created: `
+ package main
+
+ import "fmt"
+
+ func F() {
+ fmt.Println()
+ }
+ `,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ Run(t, files, func(t *testing.T, env *Env) {
+ tc.source, tc.fixed, tc.created = dedent(tc.source), dedent(tc.fixed), dedent(tc.created)
+ filename := "source.go"
+ loc := compileTemplate(env, tc.source, filename)
+ actions, err := env.Editor.CodeAction(env.Ctx, loc, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var codeAction *protocol.CodeAction
+ for _, action := range actions {
+ if action.Title == "Move to a new file" {
+ codeAction = &action
+ break
+ }
+ }
+ if codeAction == nil {
+ t.Fatal("cannot find move to a new file action")
+ }
+
+ env.ApplyCodeAction(*codeAction)
+ got := env.BufferText(filename)
+ if tc.fixed != got {
+ t.Errorf(`incorrect output of fixed file:
+source:
+%s
+got:
+%s
+want:
+%s
+`, indent(tc.source), indent(got), indent(tc.fixed))
+ }
+ gotMoved := env.BufferText(tc.createdFilename)
+ if tc.created != gotMoved {
+ t.Errorf(`incorrect output of created file:
+source:
+%s
+got created file:
+%s
+want created file:
+%s
+`, indent(tc.source), indent(gotMoved), indent(tc.created))
+ }
+
+ })
+ })
+ }
+}
+
+func TestMoveToANewFileInvalidSelection(t *testing.T) {
+ const files = `
+-- go.mod --
+module mod.com
+
+go 1.18
+-- main.go --
+package main
+
+`
+ for _, tc := range []struct {
+ name string
+ source string
+ }{
+ {
+ name: "select package declaration",
+ source: `
+ â–ˆpackage mainâ–ˆ
+ func fn() {}
+ `,
+ },
+ {
+ name: "select imports",
+ source: `
+ package main
+ â–ˆimport fmtâ–ˆ
+ `,
+ },
+ {
+ name: "select only comment",
+ source: `
+ package main
+ â–ˆ// commentâ–ˆ
+ `,
+ },
+ {
+ name: "selection does not contain whole top-level node",
+ source: `
+ package main
+ func fn() {
+ â–ˆprint(0)â–ˆ
+ }
+ `,
+ },
+ {
+ name: "selection cross a comment",
+ source: `
+ package main
+ // comâ–ˆment
+
+ func fn() {}â–ˆ
+ `,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ Run(t, files, func(t *testing.T, env *Env) {
+ filename := "source.go"
+ loc := compileTemplate(env, dedent(tc.source), filename)
+ actions, err := env.Editor.CodeAction(env.Ctx, loc, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, action := range actions {
+ if action.Title == "Move to a new file" {
+ t.Errorf("should not offer code action")
+ return
+ }
+ }
+ })
+ })
+ }
+}

Change information

Files:
  • M gopls/internal/cache/snapshot.go
  • M gopls/internal/golang/codeaction.go
  • M gopls/internal/golang/format.go
  • A gopls/internal/golang/move_to_new_file.go
  • M gopls/internal/protocol/command/command_gen.go
  • M gopls/internal/protocol/command/interface.go
  • M gopls/internal/protocol/tsdocument_changes.go
  • M gopls/internal/server/command.go
  • M gopls/internal/test/integration/fake/editor.go
  • A gopls/internal/test/integration/misc/move_to_new_file_test.go
Change size: L
Delta: 10 files changed, 844 insertions(+), 6 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
Gerrit-Change-Number: 565895
Gerrit-PatchSet: 1
Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
Gerrit-CC: Chiawen Chen <gol...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Gopher Robot (Gerrit)

unread,
Feb 21, 2024, 8:34:16 PM2/21/24
to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Gopher Robot added 1 comment

Patchset-level comments
File-level comment, Patchset 1 (Latest):
Gopher Robot . unresolved

I spotted some possible problems.

These findings are based on simple heuristics. If a finding appears wrong, briefly reply here saying so. Otherwise, please address any problems and update the GitHub PR. When complete, mark this comment as 'Done' and click the [blue 'Reply' button](https://go.dev/wiki/GerritBot#i-left-a-reply-to-a-comment-in-gerrit-but-no-one-but-me-can-see-it) above.

Possible problems detected:
1. Lines in the commit message should be wrapped at ~76 characters unless needed for things like URLs or tables. You have a 250 character line.

The commit title and commit message body come from the GitHub PR title and description, and must be edited in the GitHub web interface (not via git). For instructions, see [here](https://go.dev/wiki/GerritBot/#how-does-gerritbot-determine-the-final-commit-message). For guidelines on commit messages for the Go project, see [here](https://go.dev/doc/contribute#commit_messages).


(In general for Gerrit code reviews, the change author is expected to [log in to Gerrit](https://go-review.googlesource.com/login/) with a Gmail or other Google account and then close out each piece of feedback by marking it as 'Done' if implemented as suggested or otherwise reply to each review comment. See the [Review](https://go.dev/doc/contribute#review) section of the Contributing Guide for details.)

Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: tools
    Gerrit-Branch: master
    Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
    Gerrit-Change-Number: 565895
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-CC: Chiawen Chen <gol...@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Comment-Date: Thu, 22 Feb 2024 01:34:12 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Gerrit Bot (Gerrit)

    unread,
    Feb 21, 2024, 8:46:38 PM2/21/24
    to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

    Gerrit Bot uploaded new patchset

    Gerrit Bot uploaded patch set #2 to this change.
    Open in Gerrit

    Related details

    Attention set is empty
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: newpatchset
    Gerrit-Project: tools
    Gerrit-Branch: master
    Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
    Gerrit-Change-Number: 565895
    Gerrit-PatchSet: 2
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-CC: Chiawen Chen <gol...@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    unsatisfied_requirement
    open
    diffy

    Chiawen Chen (Gerrit)

    unread,
    Feb 22, 2024, 2:18:33 PM2/22/24
    to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Alan Donovan

    Chiawen Chen added 1 comment

    Patchset-level comments
    File-level comment, Patchset 1:
    Gopher Robot . resolved

    I spotted some possible problems.

    These findings are based on simple heuristics. If a finding appears wrong, briefly reply here saying so. Otherwise, please address any problems and update the GitHub PR. When complete, mark this comment as 'Done' and click the [blue 'Reply' button](https://go.dev/wiki/GerritBot#i-left-a-reply-to-a-comment-in-gerrit-but-no-one-but-me-can-see-it) above.

    Possible problems detected:
    1. Lines in the commit message should be wrapped at ~76 characters unless needed for things like URLs or tables. You have a 250 character line.

    The commit title and commit message body come from the GitHub PR title and description, and must be edited in the GitHub web interface (not via git). For instructions, see [here](https://go.dev/wiki/GerritBot/#how-does-gerritbot-determine-the-final-commit-message). For guidelines on commit messages for the Go project, see [here](https://go.dev/doc/contribute#commit_messages).


    (In general for Gerrit code reviews, the change author is expected to [log in to Gerrit](https://go-review.googlesource.com/login/) with a Gmail or other Google account and then close out each piece of feedback by marking it as 'Done' if implemented as suggested or otherwise reply to each review comment. See the [Review](https://go.dev/doc/contribute#review) section of the Contributing Guide for details.)

    Chiawen Chen

    Seem like a false alarm

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Alan Donovan
    Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
      Gerrit-Change-Number: 565895
      Gerrit-PatchSet: 2
      Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
      Gerrit-Reviewer: Alan Donovan <adon...@google.com>
      Gerrit-Attention: Alan Donovan <adon...@google.com>
      Gerrit-Comment-Date: Thu, 22 Feb 2024 19:18:26 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Gopher Robot <go...@golang.org>
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy

      Chiawen Chen (Gerrit)

      unread,
      Feb 22, 2024, 2:21:08 PM2/22/24
      to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
      Attention needed from Alan Donovan

      Chiawen Chen added 1 comment

      Patchset-level comments
      File-level comment, Patchset 2 (Latest):
      Chiawen Chen . resolved

      There seems to be no big problems handling comment nodes.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Alan Donovan
      Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
      Gerrit-Change-Number: 565895
      Gerrit-PatchSet: 2
      Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
      Gerrit-Reviewer: Alan Donovan <adon...@google.com>
      Gerrit-CC: Chiawen Chen <gol...@gmail.com>
      Gerrit-CC: Gopher Robot <go...@golang.org>
      Gerrit-Attention: Alan Donovan <adon...@google.com>
      Gerrit-Comment-Date: Thu, 22 Feb 2024 19:21:03 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy

      Chiawen Chen (Gerrit)

      unread,
      Feb 22, 2024, 2:36:23 PM2/22/24
      to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
      Attention needed from Alan Donovan

      Chiawen Chen added 3 comments

      File gopls/internal/cache/snapshot.go
      Line 1673, Patchset 2 (Latest): }, func() {})
      Chiawen Chen . unresolved

      I am not sure whether I need to call defer cloned.decRef() here. The docs for Snapshot.clone say it must be called but doing so cause panics.

      File gopls/internal/golang/move_to_new_file.go
      Line 128, Patchset 2 (Latest): snapshot = snapshot.Sandbox(ctx, ctx, modifications)
      Chiawen Chen . unresolved

      Not sure what to put in the second context argument.

      File gopls/internal/protocol/tsdocument_changes.go
      Line 14, Patchset 2 (Latest):// for package renaming feature. At most one field of this struct is non-nil.
      Chiawen Chen . unresolved

      I think making this into an interface with TextDocumentEdit, RenameFile, and CreateFile implementing it would make it easier to use.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Alan Donovan
      Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 2
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Thu, 22 Feb 2024 19:36:16 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        Feb 22, 2024, 2:42:53 PM2/22/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 1 comment

        File gopls/internal/cache/snapshot.go
        Line 1660, Patchset 2 (Latest):
        Chiawen Chen . unresolved

        Need to do this because the code action needs to update import declarations after moving codes, but allImportsFixes requires snapshot as an argument. So I first made code moving changes in a cloned snapshot, and then call allImportsFixes on the cloned snapshot.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 2
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Thu, 22 Feb 2024 19:42:47 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        Feb 22, 2024, 5:20:15 PM2/22/24
        to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Chiawen Chen

        Alan Donovan added 13 comments

        Patchset-level comments
        Alan Donovan . resolved

        Hi Chiawen, many thanks for contributing this feature! It looks like you've done a thorough job with testing.

        I've taken a first pass over it and added a number of comments.

        File gopls/internal/cache/snapshot.go
        Chiawen Chen . unresolved

        Need to do this because the code action needs to update import declarations after moving codes, but allImportsFixes requires snapshot as an argument. So I first made code moving changes in a cloned snapshot, and then call allImportsFixes on the cloned snapshot.

        Alan Donovan

        This seems unnecessarily complex. The required import updates can be computed from the the moved code: (1) copy all the import declarations from the original file, then, (2) for both files (old and new), delete any import declarations that aren't needed.

        To compute the necessary imports, iterate over types.Info.Uses map looking for (id, obj) pairs where obj is a PkgName in the old file. Divide them into two buckets based on whether the refactoring moved id or not. Then see which PkgNames in each bucket have no ids and delete its ImportSpec from the corresponding file.

        Alternatively, you could omit step 2 and just wait for the user to save both files, triggering goimports.

        File gopls/internal/golang/codeaction.go
        Line 89, Patchset 2 (Latest): moves, err := getMoveToNewFileCodeAction(pgf, rng, snapshot.Options())
        Alan Donovan . unresolved

        MoveToNewFile is an "extract" code action, so I think you should move it into getExtractCodeActions.

        File gopls/internal/golang/move_to_new_file.go
        Line 32, Patchset 2 (Latest): cmd, err := command.NewMoveToANewFileCommand("m", command.MoveToANewFileArgs{URI: pgf.URI, Range: rng})
        Alan Donovan . unresolved

        "Extract declarations to new file"

        (I don't think this name appear in the VS Code UI, but let's choose something meaningful. I does appear in the CLI I think.)

        Line 37, Patchset 2 (Latest): Title: "Move to a new file",
        Alan Donovan . unresolved

        "Extract declarations to new file"

        Line 127, Patchset 2 (Latest): // apply edits into a cloned snapshot and calculate import edits
        Alan Donovan . unresolved

        I don't think you need any of this section (up to L153) if you compute the appropriate import edits as described in my other comment.

        Line 154, Patchset 2 (Latest): return &protocol.WorkspaceEdit{
        Alan Donovan . unresolved

        In an ideal world the user's cursor would be moved to the new file---otherwise they may wonder what file name was created. That's possible to achieve, but a little tricky: rather than returning a WorkspaceEdit, we would have to make a Client.ApplyEdit downcall, followed by a ShowDocument downcall featuring the new URI. (The Client.ApplyEdit need contain only the file-creation edit; we could return a WorkspaceEdit for the actual changes to the text.)

        Line 156, Patchset 2 (Latest): {
        // original file edits
        TextDocumentEdit: &protocol.TextDocumentEdit{
        TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
        TextDocumentIdentifier: protocol.TextDocumentIdentifier{
        URI: fh.URI(),
        },
        Version: fh.Version(),
        },
        Edits: protocol.AsAnnotatedTextEdits(
        append(
        importEdits,
        protocol.TextEdit{
        Range: replaceRange,
        NewText: "",
        }),
        ),
        },
        },
        Alan Donovan . unresolved

        TextEditsToDocumentChanges(fh.URI(), fh.Version(), edits...)

        Line 183, Patchset 2 (Latest): TextDocumentEdit: &protocol.TextDocumentEdit{
        Alan Donovan . unresolved

        TextEditsToDocumentChanges(createFileURI, 0, edits...)

        Line 207, Patchset 2 (Latest): if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
        Alan Donovan . unresolved

        Code in package golang should not access the file system directly; instead ask the snapshot for the content of the file. This ensures (a) that the analysis operates on a consistent view even if the file system is changing underfoot, and (b) that the dependency analysis knows which files were relevant.

        Line 221, Patchset 2 (Latest):func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . unresolved

        Take a look at PathEnclosingInterval(file, start, end). I think you just need to walk up to the enclosing Decl immediately before the File.

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2 (Latest):func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . unresolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Line 54, Patchset 2 (Latest):-- filealreadexists.go --
        Alan Donovan . unresolved

        existing.go
        existing2.go
        etc

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 2
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        Gerrit-Comment-Date: Thu, 22 Feb 2024 22:20:11 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Chiawen Chen <gol...@gmail.com>
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        Feb 23, 2024, 2:26:18 PM2/23/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan and Chiawen Chen

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #3 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 3
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        Feb 23, 2024, 2:27:21 PM2/23/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 16 comments

        Patchset-level comments
        File-level comment, Patchset 2:
        Chiawen Chen . resolved

        Thanks for the thorough code review! I have pushed a new commit and added some comments.

        File gopls/internal/cache/snapshot.go
        Chiawen Chen . unresolved

        Need to do this because the code action needs to update import declarations after moving codes, but allImportsFixes requires snapshot as an argument. So I first made code moving changes in a cloned snapshot, and then call allImportsFixes on the cloned snapshot.

        Alan Donovan

        This seems unnecessarily complex. The required import updates can be computed from the the moved code: (1) copy all the import declarations from the original file, then, (2) for both files (old and new), delete any import declarations that aren't needed.

        To compute the necessary imports, iterate over types.Info.Uses map looking for (id, obj) pairs where obj is a PkgName in the old file. Divide them into two buckets based on whether the refactoring moved id or not. Then see which PkgNames in each bucket have no ids and delete its ImportSpec from the corresponding file.

        Alternatively, you could omit step 2 and just wait for the user to save both files, triggering goimports.

        Chiawen Chen

        Sure! I implemented your approach in the new commits. Omitting the import fixes is okay but not ideal because it leaves the files in warnings after taking the code action.

        Line 1673, Patchset 2: }, func() {})
        Chiawen Chen . resolved

        I am not sure whether I need to call defer cloned.decRef() here. The docs for Snapshot.clone say it must be called but doing so cause panics.

        Chiawen Chen

        Done

        File gopls/internal/golang/codeaction.go
        Line 89, Patchset 2: moves, err := getMoveToNewFileCodeAction(pgf, rng, snapshot.Options())
        Alan Donovan . resolved

        MoveToNewFile is an "extract" code action, so I think you should move it into getExtractCodeActions.

        Chiawen Chen

        Done

        File gopls/internal/golang/move_to_new_file.go
        Line 32, Patchset 2: cmd, err := command.NewMoveToANewFileCommand("m", command.MoveToANewFileArgs{URI: pgf.URI, Range: rng})
        Alan Donovan . resolved

        "Extract declarations to new file"

        (I don't think this name appear in the VS Code UI, but let's choose something meaningful. I does appear in the CLI I think.)

        Chiawen Chen

        Done

        Line 37, Patchset 2: Title: "Move to a new file",
        Alan Donovan . resolved

        "Extract declarations to new file"

        Chiawen Chen

        Done

        Line 127, Patchset 2: // apply edits into a cloned snapshot and calculate import edits
        Alan Donovan . resolved

        I don't think you need any of this section (up to L153) if you compute the appropriate import edits as described in my other comment.

        Chiawen Chen

        Done

        Line 128, Patchset 2: snapshot = snapshot.Sandbox(ctx, ctx, modifications)
        Chiawen Chen . resolved

        Not sure what to put in the second context argument.

        Chiawen Chen

        Done

        Line 154, Patchset 2: return &protocol.WorkspaceEdit{
        Alan Donovan . unresolved

        In an ideal world the user's cursor would be moved to the new file---otherwise they may wonder what file name was created. That's possible to achieve, but a little tricky: rather than returning a WorkspaceEdit, we would have to make a Client.ApplyEdit downcall, followed by a ShowDocument downcall featuring the new URI. (The Client.ApplyEdit need contain only the file-creation edit; we could return a WorkspaceEdit for the actual changes to the text.)

        Chiawen Chen

        In vscode the CreateFile operation will show the created file in an opened tab (not focused) and shown as unsaved, This should be enough for users to take notice about it. For other editors I have no idea.


        // original file edits
        TextDocumentEdit: &protocol.TextDocumentEdit{
        TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
        TextDocumentIdentifier: protocol.TextDocumentIdentifier{
        URI: fh.URI(),
        },
        Version: fh.Version(),
        },
        Edits: protocol.AsAnnotatedTextEdits(
        append(
        importEdits,
        protocol.TextEdit{
        Range: replaceRange,
        NewText: "",
        }),
        ),
        },
        },
        Alan Donovan . resolved

        TextEditsToDocumentChanges(fh.URI(), fh.Version(), edits...)

        Chiawen Chen

        Done

        Line 183, Patchset 2: TextDocumentEdit: &protocol.TextDocumentEdit{
        Alan Donovan . resolved

        TextEditsToDocumentChanges(createFileURI, 0, edits...)

        Chiawen Chen

        Done

        Line 207, Patchset 2: if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
        Alan Donovan . unresolved

        Code in package golang should not access the file system directly; instead ask the snapshot for the content of the file. This ensures (a) that the analysis operates on a consistent view even if the file system is changing underfoot, and (b) that the dependency analysis knows which files were relevant.

        Chiawen Chen

        I met problem on checking the existence of a file using a snapshot. The closest solution I find is snapshot.ReadFile, but it succeeds even if the file does not exists, and the returned file handle does not seem to contain enough informations.

        Line 221, Patchset 2:func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . unresolved

        Take a look at PathEnclosingInterval(file, start, end). I think you just need to walk up to the enclosing Decl immediately before the File.

        Chiawen Chen

        Sorry but I am not sure what to proceed with this comment.

        File gopls/internal/protocol/tsdocument_changes.go
        Line 14, Patchset 2:// for package renaming feature. At most one field of this struct is non-nil.
        Chiawen Chen . resolved

        I think making this into an interface with TextDocumentEdit, RenameFile, and CreateFile implementing it would make it easier to use.

        Chiawen Chen

        Done

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2:func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . unresolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Chiawen Chen

        I tried to write tests in the marker framework, but it does not work for now because the testing infrastructure is missing some implementations around file creation edits. Earlier I decided not to write tests as marker tests because I thought the marker framework cannot express testing of created files (content and filename), but now I think again it probably could.

        Should this be a priority I can try to implement the missing testing infrastructure.

        Line 54, Patchset 2:-- filealreadexists.go --
        Alan Donovan . resolved

        existing.go
        existing2.go
        etc

        Chiawen Chen

        Done

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 2
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Fri, 23 Feb 2024 19:27:14 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Alan Donovan <adon...@google.com>
        Comment-In-Reply-To: Chiawen Chen <gol...@gmail.com>
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        Feb 23, 2024, 2:32:36 PM2/23/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #4 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 4
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        Feb 27, 2024, 4:37:34 PM2/27/24
        to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Chiawen Chen

        Alan Donovan added 39 comments

        Patchset-level comments
        File-level comment, Patchset 4 (Latest):
        Alan Donovan . resolved

        Just another partial pass--lots of comments again, but it's moving in the right direction.

        File gopls/internal/golang/codeaction.go
        Line 183, Patchset 4 (Latest): var actions []protocol.CodeAction

        extractToNewFileActions, err := getExtractToNewFileCodeActions(pgf, rng, options)
        if err != nil {
        return nil, err
        }
        actions = append(actions, extractToNewFileActions...)
        Alan Donovan . unresolved

        We might want to move this step after the Start==End check, so that you have to select some text to get the action. That seems a little more intuitive to me; it also reduces the amount of work in the common case of typing, when Start==End.

        Line 195, Patchset 4 (Latest): start, end, err := pgf.RangePos(rng)
        Alan Donovan . unresolved

        Pass start and end to getExtractToNewFileCommand, so it needn't call RangePos.

        Line 200, Patchset 4 (Latest): var commands []protocol.Command
        Alan Donovan . unresolved

        Append the new refactoring to this list of commands, so that the existing loop at L237 does its thing; then getExtractToNewFileCodeActions needn't return an action, just the command.

        File gopls/internal/golang/move_to_new_file.go
        Line 9, Patchset 4 (Latest):// todo: rename file to extract_to_new_file.go after code review
        Alan Donovan . unresolved

        You can do it during the review. I suggest extracttofile.go.

        Line 31, Patchset 4 (Latest):func getExtractToNewFileCodeActions(pgf *parsego.File, rng protocol.Range, _ *settings.Options) ([]protocol.CodeAction, error) {
        Alan Donovan . unresolved

        Command

        and return (cmd, err). The CodeAction can be created by the common logic in codeaction.go.

        Line 36, Patchset 4 (Latest): cmd, err := command.NewExtractToNewFileCommand(
        Alan Donovan . unresolved

        return

        Line 76, Patchset 4 (Latest):func findImportEdits(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (adds []*ast.ImportSpec, deletes []*ast.ImportSpec) {
        Alan Donovan . unresolved

        file *ast.File, info *types.Info

        and call with (pgf.File, pkg.GetTypesInfo())

        Line 77, Patchset 4 (Latest): var (
        Alan Donovan . unresolved

        Each step of a complex algorithm should be documented:


        // Record which PkgNames are referenced from within the
        // extracted selection, and from outside of it. Those in the
        // extracted selection must be imported in the new file.
        // Those only in the extracted selection must be deleted
        // from the original file.

        Line 90, Patchset 4 (Latest): type NamePath struct {
        Alan Donovan . unresolved

        Use uncapitalized names (x3).

        Line 92, Patchset 4 (Latest): Path string
        Alan Donovan . unresolved

        pkg *types.Package

        and use 
        typesutil.ImportedPkgName(info, spec) at L99, L101
        and
        pkgName.Imported() (without .Path()) at L106, 108.
        Line 96, Patchset 4 (Latest): for _, v := range pgf.File.Imports {
        Alan Donovan . unresolved

        spec

        Line 97, Patchset 4 (Latest): path := strings.Trim(v.Path.Value, `"`)
        Alan Donovan . unresolved

        strconv.Unquote(spec.Path.Value)

        or metadata.UnquoteImportPath(spec), and change the type of Path string to ImportPath.

        Line 107, Patchset 4 (Latest): if importSpec == nil {
        Alan Donovan . unresolved

        How can this happen? If it was foundInSelection, there must have been an ImportSpec, and PkgName and a Package.

        Line 130, Patchset 4 (Latest): start, end, err := pgf.RangePos(rng)
        if err != nil {

        return nil, fmt.Errorf("%s: %w", errorPrefix, err)
        }

        start, end, filename, err := findRangeAndFilename(pgf, start, end)
        if err != nil {

        return nil, fmt.Errorf("%s: %w", errorPrefix, err)
        }

        if dry {
        return nil, nil
        Alan Donovan . unresolved

        This is a false abstraction. The only real work in the "dry" case is findRangeAndFilename. So I suggest you call RangePos and findRangeAndFilename directly from codeaction.go.

        Line 144, Patchset 4 (Latest): end = skipWhiteSpaces(pgf, end)
        Alan Donovan . unresolved

        Is this necessary? I think it would be fine to replace the selected declarations by "\n" in the source file, and let the formatter tidy up the whitespace on the next save.

        Line 148, Patchset 4 (Latest): return nil, fmt.Errorf("%s: %w", errorPrefix, err)
        Alan Donovan . unresolved

        return nil, bug.Reportf("findRangeAndFilename returned invalid range: %v", err)

        Line 154, Patchset 4 (Latest): parenthesisFreeImports := findParenthesisFreeImports(pgf)
        Alan Donovan . unresolved

        I wonder whether this is necessary. If we delete the sole ImportSpec from an import GenDecl, won't the formatter eliminate the declaration entirely? Or does it replace it with import ()? Or "import \n", which doesn't compile?

        In any case, this section needs more commentary.

        Line 176, Patchset 4 (Latest): createFileURI, err := resolveCreateFileURI(pgf.URI.Dir().Path(), filename)
        Alan Donovan . unresolved

        newFileURI

        Line 181, Patchset 4 (Latest): creatFileText, err := format.Source([]byte(
        Alan Donovan . unresolved

        newContent

        Line 181, Patchset 4 (Latest): creatFileText, err := format.Source([]byte(
        Alan Donovan . unresolved

        TODO: attempt to duplicate the copyright header, if any.

        Line 207, Patchset 2: if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
        Alan Donovan . unresolved

        Code in package golang should not access the file system directly; instead ask the snapshot for the content of the file. This ensures (a) that the analysis operates on a consistent view even if the file system is changing underfoot, and (b) that the dependency analysis knows which files were relevant.

        Chiawen Chen

        I met problem on checking the existence of a file using a snapshot. The closest solution I find is snapshot.ReadFile, but it succeeds even if the file does not exists, and the returned file handle does not seem to contain enough informations.

        Alan Donovan

        That's right, ReadFile always returns a handle. Check the error from Handle.Content() to test whether the file exists.

        Line 221, Patchset 2:func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . resolved

        Take a look at PathEnclosingInterval(file, start, end). I think you just need to walk up to the enclosing Decl immediately before the File.

        Chiawen Chen

        Sorry but I am not sure what to proceed with this comment.

        Alan Donovan

        Never mind, I see now that it wouldn't help in this situation.

        Line 237, Patchset 4 (Latest):func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . unresolved

        // selectedToplevelDecls returns the lexical extent of the top-level
        // declarations enclosing (start, end), along with the name of the
        // first declaration. It returns zeros on error.
        func selectedToplevelDecls

        Line 237, Patchset 4 (Latest):func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . unresolved

        You don't need to return an error, since it is ignored in the dryrun mode, and it "can't happen" in the real mode. Just return (0, 0, "") on error, and document this.

        Line 238, Patchset 4 (Latest): if intersect(start, end, pgf.File.Package, pgf.File.Name.End()) {
        return 0, 0, "", errors.New("selection cannot intersect a package declaration")
        }
        Alan Donovan . unresolved

        This isn't necessary. If only package decl is selected, the loop below will be a no-op and the function will return an error.

        I think it's fine to be permissive: the only declarations that matter are the ones that start after the imports. If stuff before that is selected, pretend it isn't.

        Line 242, Patchset 4 (Latest): for _, node := range pgf.File.Decls {
        Alan Donovan . unresolved

        decl

        Line 244, Patchset 4 (Latest): if v, ok := node.(*ast.GenDecl); ok && v.Tok == token.IMPORT {
        Alan Donovan . unresolved

        Use a type switch.

        Line 251, Patchset 4 (Latest): if v, ok := node.(*ast.FuncDecl); ok && contain(v.Pos(), v.Name.End(), start, end) {
        Alan Donovan . unresolved

        I don't think it's necessary that the name be selected.

        if ok {...}

        Line 255, Patchset 4 (Latest): if v, ok := node.(*ast.GenDecl); ok && (v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
        Alan Donovan . unresolved

        Again I don't think the contains checks are necessary.

        Line 255, Patchset 4 (Latest): if v, ok := node.(*ast.GenDecl); ok && (v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
        Alan Donovan . unresolved

        Simpler: Tok != types.IMPORT

        Combine with the case for IMPORT:

        case *ast.GenDecl: 
        if Tok == types.IMPORT { return ... }
        ...
        Line 260, Patchset 4 (Latest): if !contain(start, end, node.Pos(), node.End()) {
        Alan Donovan . unresolved

        Why not? If I select most of two adjacent functions, it's pretty clear what to do.

        Line 267, Patchset 4 (Latest): if c := getCommentGroup(node); c != nil {
        if c.Pos() < start {
        Alan Donovan . unresolved

        &&

        Line 275, Patchset 4 (Latest): for _, node := range pgf.File.Comments {
        Alan Donovan . unresolved

        comment

        Line 277, Patchset 4 (Latest): if !contain(start, end, node.Pos(), node.End()) {
        return 0, 0, "", errors.New("selection cannot partially intersect a comment")
        }
        Alan Donovan . unresolved

        No need for an error; reduce start to comment.Pos, or increase end to comment.End.

        Line 292, Patchset 4 (Latest): if c == ' ' || c == '\t' || c == '\n' {
        continue
        } else {
        Alan Donovan . unresolved

        if !... { break }

        There's no need for a continue and a break.

        Line 301, Patchset 4 (Latest):func getCommentGroup(node ast.Node) *ast.CommentGroup {
        Alan Donovan . unresolved

        Inline this function to its sole call.

        Line 332, Patchset 4 (Latest):func getNodeName(node ast.Node) string {
        Alan Donovan . unresolved

        Combine this with the main loop over decls, which already switches between decl types:

        	for ... decls {
        var id *ast.Ident
        switch ... {
        case *ast.FuncDecl:
        id = decl.Name
        ...
        }
        if id != nil && firstName != "" { firstName = id.Name }
        }
        File gopls/internal/protocol/command/interface.go
        Line 151, Patchset 4 (Latest): // ExtractToNewFile: Move selected codes to a new file
        Alan Donovan . unresolved

        declarations

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 4
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        Gerrit-Comment-Date: Tue, 27 Feb 2024 21:37:29 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Chiawen Chen <gol...@gmail.com>
        Comment-In-Reply-To: Alan Donovan <adon...@google.com>
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        Feb 28, 2024, 1:41:00 PM2/28/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan and Chiawen Chen

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #5 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 5
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        Feb 28, 2024, 1:41:14 PM2/28/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 34 comments

        Patchset-level comments
        File-level comment, Patchset 4:
        Chiawen Chen . resolved

        Some edits

        File gopls/internal/golang/codeaction.go
        Line 183, Patchset 4: var actions []protocol.CodeAction


        extractToNewFileActions, err := getExtractToNewFileCodeActions(pgf, rng, options)
        if err != nil {
        return nil, err
        }
        actions = append(actions, extractToNewFileActions...)
        Alan Donovan . unresolved

        We might want to move this step after the Start==End check, so that you have to select some text to get the action. That seems a little more intuitive to me; it also reduces the amount of work in the common case of typing, when Start==End.

        Chiawen Chen

        This is handy for when a user place cursor on the function name (single click) to get code action.

        Another code action that allows zero-width selection in gopls is inline call.

        Line 195, Patchset 4: start, end, err := pgf.RangePos(rng)
        Alan Donovan . resolved

        Pass start and end to getExtractToNewFileCommand, so it needn't call RangePos.

        Chiawen Chen

        Done

        Line 200, Patchset 4: var commands []protocol.Command
        Alan Donovan . resolved

        Append the new refactoring to this list of commands, so that the existing loop at L237 does its thing; then getExtractToNewFileCodeActions needn't return an action, just the command.

        Chiawen Chen

        Done

        File gopls/internal/golang/move_to_new_file.go
        Line 31, Patchset 4:func getExtractToNewFileCodeActions(pgf *parsego.File, rng protocol.Range, _ *settings.Options) ([]protocol.CodeAction, error) {
        Alan Donovan . resolved

        Command

        and return (cmd, err). The CodeAction can be created by the common logic in codeaction.go.

        Chiawen Chen

        Done

        Line 36, Patchset 4: cmd, err := command.NewExtractToNewFileCommand(
        Alan Donovan . resolved

        return

        Chiawen Chen

        Done

        Line 76, Patchset 4:func findImportEdits(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (adds []*ast.ImportSpec, deletes []*ast.ImportSpec) {
        Alan Donovan . resolved

        file *ast.File, info *types.Info

        and call with (pgf.File, pkg.GetTypesInfo())

        Chiawen Chen

        Done

        Line 77, Patchset 4: var (
        Alan Donovan . resolved

        Each step of a complex algorithm should be documented:


        // Record which PkgNames are referenced from within the
        // extracted selection, and from outside of it. Those in the
        // extracted selection must be imported in the new file.
        // Those only in the extracted selection must be deleted
        // from the original file.

        Chiawen Chen

        Done

        Line 90, Patchset 4: type NamePath struct {
        Alan Donovan . resolved

        Use uncapitalized names (x3).

        Chiawen Chen

        Done

        Alan Donovan . unresolved

        pkg *types.Package

        and use 
        typesutil.ImportedPkgName(info, spec) at L99, L101
        and
        pkgName.Imported() (without .Path()) at L106, 108.
        Chiawen Chen

        I made a big rewrite of the function using typesutil.ImportedPkgName. I believe the rewrite is easier to follow.

        Also I found that my implementation does not handle dot imports correctly. If the extracted section contains identifiers defined by a dot import, the dot import should be added to the new file.

        I left the bug unfixed in the new commit.

        Line 96, Patchset 4: for _, v := range pgf.File.Imports {
        Alan Donovan . resolved

        spec

        Chiawen Chen

        Done

        Line 97, Patchset 4: path := strings.Trim(v.Path.Value, `"`)
        Alan Donovan . resolved

        strconv.Unquote(spec.Path.Value)

        or metadata.UnquoteImportPath(spec), and change the type of Path string to ImportPath.

        Chiawen Chen

        Done

        Line 107, Patchset 4: if importSpec == nil {
        Alan Donovan . unresolved

        How can this happen? If it was foundInSelection, there must have been an ImportSpec, and PkgName and a Package.

        Chiawen Chen

        There is a perfect reason this happens but it is moot after I rewrite function with typesutil.ImportedPkgName.

        Line 130, Patchset 4: start, end, err := pgf.RangePos(rng)

        if err != nil {
        return nil, fmt.Errorf("%s: %w", errorPrefix, err)
        }

        start, end, filename, err := findRangeAndFilename(pgf, start, end)
        if err != nil {
        return nil, fmt.Errorf("%s: %w", errorPrefix, err)
        }

        if dry {
        return nil, nil
        Alan Donovan . resolved

        This is a false abstraction. The only real work in the "dry" case is findRangeAndFilename. So I suggest you call RangePos and findRangeAndFilename directly from codeaction.go.

        Chiawen Chen

        Done

        Line 144, Patchset 4: end = skipWhiteSpaces(pgf, end)
        Alan Donovan . unresolved

        Is this necessary? I think it would be fine to replace the selected declarations by "\n" in the source file, and let the formatter tidy up the whitespace on the next save.

        Chiawen Chen

        This is not necessary but nice to have. Otherwise the code action outputs 3 consecutive empty lines in the most common case.

        Line 148, Patchset 4: return nil, fmt.Errorf("%s: %w", errorPrefix, err)
        Alan Donovan . resolved

        return nil, bug.Reportf("findRangeAndFilename returned invalid range: %v", err)

        Chiawen Chen

        Done

        Line 154, Patchset 4: parenthesisFreeImports := findParenthesisFreeImports(pgf)
        Alan Donovan . unresolved

        I wonder whether this is necessary. If we delete the sole ImportSpec from an import GenDecl, won't the formatter eliminate the declaration entirely? Or does it replace it with import ()? Or "import \n", which doesn't compile?

        In any case, this section needs more commentary.

        Chiawen Chen

        I have added the following comment:

        ```
        // For unparenthesised declarations like `import "fmt"` we remove
        // the whole declaration because simply removing importSpec leaves
        // `import \n`, which does not compile.
        // For parenthesised declarations like `import ("fmt"\n "log")`
        // we only remove the ImportSpec, because removing the whole declaration
        // might remove other ImportsSpecs we don't want to touch.
        ```

        Line 176, Patchset 4: createFileURI, err := resolveCreateFileURI(pgf.URI.Dir().Path(), filename)
        Alan Donovan . resolved

        newFileURI

        Chiawen Chen

        Done

        Line 181, Patchset 4: creatFileText, err := format.Source([]byte(
        Alan Donovan . resolved

        TODO: attempt to duplicate the copyright header, if any.

        Chiawen Chen

        Done

        Line 181, Patchset 4: creatFileText, err := format.Source([]byte(
        Alan Donovan . resolved

        newContent

        Chiawen Chen

        Done

        Line 207, Patchset 2: if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
        Alan Donovan . resolved

        Code in package golang should not access the file system directly; instead ask the snapshot for the content of the file. This ensures (a) that the analysis operates on a consistent view even if the file system is changing underfoot, and (b) that the dependency analysis knows which files were relevant.

        Chiawen Chen

        I met problem on checking the existence of a file using a snapshot. The closest solution I find is snapshot.ReadFile, but it succeeds even if the file does not exists, and the returned file handle does not seem to contain enough informations.

        Alan Donovan

        That's right, ReadFile always returns a handle. Check the error from Handle.Content() to test whether the file exists.

        Chiawen Chen

        Done

        Line 237, Patchset 4:func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . unresolved

        // selectedToplevelDecls returns the lexical extent of the top-level
        // declarations enclosing (start, end), along with the name of the
        // first declaration. It returns zeros on error.
        func selectedToplevelDecls

        Chiawen Chen

        This description does not agree with what I have in my mind:
        1. partial selection of a declaration should not be offered code action.
        2. as an exception selecting only function name should be offered code action.

        I modified the comment to this:


        // selectedToplevelDecls returns the lexical extent of the top-level

        // declarations enclosed by [start, end), along with the name of the
        // first declaration. The returned boolean reports whether the selection
        // should be offered code action.

        Line 237, Patchset 4:func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . unresolved

        You don't need to return an error, since it is ignored in the dryrun mode, and it "can't happen" in the real mode. Just return (0, 0, "") on error, and document this.

        Chiawen Chen

        I changed the error return value to an ok boolean.

        Line 238, Patchset 4: if intersect(start, end, pgf.File.Package, pgf.File.Name.End()) {

        return 0, 0, "", errors.New("selection cannot intersect a package declaration")
        }
        Alan Donovan . unresolved

        This isn't necessary. If only package decl is selected, the loop below will be a no-op and the function will return an error.

        I think it's fine to be permissive: the only declarations that matter are the ones that start after the imports. If stuff before that is selected, pretend it isn't.

        Chiawen Chen

        I think the main concern of this comment is about the unnecessary use of an error. I changed the error return value to an ok boolean. If you are concerned about that the code action should be offered when package decl is selected, let me know.

        Line 242, Patchset 4: for _, node := range pgf.File.Decls {
        Alan Donovan . resolved

        decl

        Chiawen Chen

        Done

        Line 244, Patchset 4: if v, ok := node.(*ast.GenDecl); ok && v.Tok == token.IMPORT {
        Alan Donovan . resolved

        Use a type switch.

        Chiawen Chen

        Done

        Line 251, Patchset 4: if v, ok := node.(*ast.FuncDecl); ok && contain(v.Pos(), v.Name.End(), start, end) {
        Alan Donovan . unresolved

        I don't think it's necessary that the name be selected.

        if ok {...}

        Chiawen Chen

        I think you read it in reverse, this reads as "function name contains the selection", not the other way.

        This is useful when a user want to refactor out a function and double click select the function name.

        Line 260, Patchset 4: if !contain(start, end, node.Pos(), node.End()) {
        Alan Donovan . unresolved

        Why not? If I select most of two adjacent functions, it's pretty clear what to do.

        Chiawen Chen

        There are two reasons not to offer code action for partial selection:

        1. There is an ambiguity for when there is a type declaration inside of a function body, and only that declaration is selected. A user might want to extract only that type declaration but instead we extract the ambient function.
        Another ambiguity is when a var declaration declared several variables, but only one of them is selected, a user need to guess wether only the selected value specifier is extracted, or the whole variable declaration is extracted.

        2. The user story is that a user want to refactor codes and select the code section intended to move. If a user only selected part of a function, the user often don't want this code action, then offering it is a noise.

        Line 267, Patchset 4: if c := getCommentGroup(node); c != nil {
        if c.Pos() < start {
        Alan Donovan . resolved

        &&

        Chiawen Chen

        Done

        Line 275, Patchset 4: for _, node := range pgf.File.Comments {
        Alan Donovan . resolved

        comment

        Chiawen Chen

        Done

        Line 277, Patchset 4: if !contain(start, end, node.Pos(), node.End()) {

        return 0, 0, "", errors.New("selection cannot partially intersect a comment")
        }
        Alan Donovan . unresolved

        No need for an error; reduce start to comment.Pos, or increase end to comment.End.

        Chiawen Chen

        When a comment is partially selected, does the user want the comment extracted or not? If we choose to extract it, some users will be surprised, if we chose not to extract it, some others user will be surprised. Not offering code action in this case is a fine way to eliminate this ambiguity.

        Line 292, Patchset 4: if c == ' ' || c == '\t' || c == '\n' {
        continue
        } else {
        Alan Donovan . resolved

        if !... { break }

        There's no need for a continue and a break.

        Chiawen Chen

        Done

        Line 301, Patchset 4:func getCommentGroup(node ast.Node) *ast.CommentGroup {
        Alan Donovan . resolved

        Inline this function to its sole call.

        Chiawen Chen

        Done

        Line 332, Patchset 4:func getNodeName(node ast.Node) string {
        Alan Donovan . resolved

        Combine this with the main loop over decls, which already switches between decl types:

        	for ... decls {
        var id *ast.Ident
        switch ... {
        case *ast.FuncDecl:
        id = decl.Name
        ...
        }
        if id != nil && firstName != "" { firstName = id.Name }
        }
        Chiawen Chen

        Done

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 4
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Wed, 28 Feb 2024 18:41:04 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        Mar 12, 2024, 9:20:58 PM3/12/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 2 comments

        Patchset-level comments
        File-level comment, Patchset 5 (Latest):
        Chiawen Chen . resolved

        This is ready for review

        File gopls/internal/protocol/command/interface.go
        Line 151, Patchset 4: // ExtractToNewFile: Move selected codes to a new file
        Alan Donovan . resolved

        declarations

        Chiawen Chen

        Done

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 5
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Wed, 13 Mar 2024 01:20:50 +0000
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        Mar 12, 2024, 9:24:43 PM3/12/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #6 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 6
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        May 14, 2024, 4:42:36 PM5/14/24
        to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Chiawen Chen

        Alan Donovan added 37 comments

        Patchset-level comments
        File-level comment, Patchset 6 (Latest):
        Alan Donovan . resolved

        Thanks Chiawen. Sorry for the long delay, and for all the comments, though most should be easy to deal with. The production code is close to ready; and the tests should be expressible in the marker framework very soon (see below).

        Feel free to resolve the comments you think are resolved.

        File gopls/internal/cache/snapshot.go
        Line 1660, Patchset 2:
        Chiawen Chen . resolved

        Need to do this because the code action needs to update import declarations after moving codes, but allImportsFixes requires snapshot as an argument. So I first made code moving changes in a cloned snapshot, and then call allImportsFixes on the cloned snapshot.

        Alan Donovan

        This seems unnecessarily complex. The required import updates can be computed from the the moved code: (1) copy all the import declarations from the original file, then, (2) for both files (old and new), delete any import declarations that aren't needed.

        To compute the necessary imports, iterate over types.Info.Uses map looking for (id, obj) pairs where obj is a PkgName in the old file. Divide them into two buckets based on whether the refactoring moved id or not. Then see which PkgNames in each bucket have no ids and delete its ImportSpec from the corresponding file.

        Alternatively, you could omit step 2 and just wait for the user to save both files, triggering goimports.

        Chiawen Chen

        Sure! I implemented your approach in the new commits. Omitting the import fixes is okay but not ideal because it leaves the files in warnings after taking the code action.

        Alan Donovan

        Thanks.

        File gopls/internal/golang/codeaction.go
        Line 183, Patchset 4: var actions []protocol.CodeAction

        extractToNewFileActions, err := getExtractToNewFileCodeActions(pgf, rng, options)
        if err != nil {
        return nil, err
        }
        actions = append(actions, extractToNewFileActions...)
        Alan Donovan . resolved

        We might want to move this step after the Start==End check, so that you have to select some text to get the action. That seems a little more intuitive to me; it also reduces the amount of work in the common case of typing, when Start==End.

        Chiawen Chen

        This is handy for when a user place cursor on the function name (single click) to get code action.

        Another code action that allows zero-width selection in gopls is inline call.

        Alan Donovan

        OK, having played with the feature it seems reasonable.

        File gopls/internal/golang/extracttofile.go
        Line 38, Patchset 6 (Latest):// TODO: handle dot imports
        Alan Donovan . unresolved

        We should at least detect dot imports and abort the operation for now; we (or you if you're willing) can add support for them later.

        Line 78, Patchset 6 (Latest):func ExtractToNewFile(
        ctx context.Context,
        snapshot *cache.Snapshot,
        fh file.Handle,
        rng protocol.Range,
        Alan Donovan . unresolved

        Join lines.

        Line 96, Patchset 6 (Latest): start, end, filename, ok := selectedToplevelDecls(pgf, start, end)
        Alan Donovan . unresolved

        firstSymbol

        Line 108, Patchset 6 (Latest): adds, deletes := findImportEdits(pgf.File, pkg.GetTypesInfo(), start, end)
        Alan Donovan . unresolved

        (The "Get" prefix has since been removed.)

        Line 126, Patchset 6 (Latest): importAdds := ""
        Alan Donovan . unresolved

        Let's use a bytes.Buffer here, and start by writing

          fmt.Fprintf(&buf, "package %s\n", pgf.File.Name.Name)

        followed by the imports. Then:
          newFileContent, err := format.Source(buf.Bytes())
        Line 154, Patchset 6 (Latest): return &protocol.WorkspaceEdit{
        Alan Donovan . unresolved

        See CL 585277, which should make this slightly easier for you. This should simplify to:

        ```
        edits := append(edits, protocol.TextEdit{Range: replaceRange, NewText: ""})
        return NewWorkspaceEdit(
        protocol.DocumentChangeEdit(fh, edits)
        protocol.DocumentChangeCreate(newFileURI),
        protocol.DocumentChangeEdit(newFileURI, []protocol.TextEdit{{NewText: string(newFileContent)}), nil
        ```

        (You'll need to define protocol.DocumentChangeCreate.)

        Line 181, Patchset 6 (Latest):// resolveNewFileURI checks that basename.go does not exists in dir, otherwise

        // select basename.{1,2,3,4,5}.go as filename.
        Alan Donovan . unresolved

        "chooses a new filename in dir, based on the name of the first extracted symbol, and if necessary to disambiguate, a numeric suffix."

        Line 183, Patchset 6 (Latest):func resolveNewFileURI(ctx context.Context, snapshot *cache.Snapshot, dir string, basename string) (protocol.DocumentURI, error) {
        Alan Donovan . unresolved

        firstSymbol

        Line 183, Patchset 6 (Latest):func resolveNewFileURI(ctx context.Context, snapshot *cache.Snapshot, dir string, basename string) (protocol.DocumentURI, error) {
        Alan Donovan . unresolved

        chooseNewFileURI

        Line 184, Patchset 6 (Latest): basename = strings.ToLower(basename)
        Alan Donovan . unresolved

        basename := strings.ToLower(firstSymbol)

        Line 189, Patchset 6 (Latest): return "", nil
        Alan Donovan . unresolved

        return "", err // e.g. cancelled

        Line 192, Patchset 6 (Latest): break
        Alan Donovan . unresolved

        Change this function to return the non-existent file.Handle on success, and return (fh, nil) here. It will make the calling code more convenient.

        Line 194, Patchset 6 (Latest): if count >= 5 {
        Alan Donovan . unresolved

        Change the loop to

             for count := 1; count < 5; count++ {

        and make the final statement
            return nil, fmt.Errorf("resolveNewFileURI: exceeded retry limit")
        Line 206, Patchset 6 (Latest):// should be offered code action.
        Alan Donovan . unresolved

        "offered a code action to extract the declarations"

        Line 233, Patchset 6 (Latest): if v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
        Alan Donovan . unresolved

        len("type")

        ditto below

        Line 282, Patchset 6 (Latest): i := pos
        Alan Donovan . unresolved

        This can be simplified to:

        i := pgf.Tok.Offset(pos)
        rest := pgf.Src[i:]
        return pos + len(rest) - len(bytes.TrimLeft(rest, " \t\n"))

        Then you don't need the skipWhiteSpaces function. (Inline the call.)

        Line 308, Patchset 6 (Latest): rng, _ := pgf.PosRange(node.Pos(), node.End())
        Alan Donovan . unresolved

        Don't discard the error here. Use bug.Report if it's supposedly impossible.

        Line 318, Patchset 6 (Latest):func contain(a, b, c, d token.Pos) bool {
        Alan Donovan . unresolved

        These verbs should be indicative not imperative, since they are predicates, not actions, so add an 's'. Let's also add a "posRange" prefix since their scope is very large:

        posRangeContains
        posRangeIntersects

        File gopls/internal/golang/move_to_new_file.go
        Line 9, Patchset 4:// todo: rename file to extract_to_new_file.go after code review
        Alan Donovan . resolved

        You can do it during the review. I suggest extracttofile.go.

        Alan Donovan

        Acknowledged

        Alan Donovan . resolved

        pkg *types.Package

        and use 
        typesutil.ImportedPkgName(info, spec) at L99, L101
        and
        pkgName.Imported() (without .Path()) at L106, 108.
        Chiawen Chen

        I made a big rewrite of the function using typesutil.ImportedPkgName. I believe the rewrite is easier to follow.

        Also I found that my implementation does not handle dot imports correctly. If the extracted section contains identifiers defined by a dot import, the dot import should be added to the new file.

        I left the bug unfixed in the new commit.

        Alan Donovan

        That's fine; just make sure we report an error.

        The code is much clearer.

        Line 107, Patchset 4: if importSpec == nil {
        Alan Donovan . resolved

        How can this happen? If it was foundInSelection, there must have been an ImportSpec, and PkgName and a Package.

        Chiawen Chen

        There is a perfect reason this happens but it is moot after I rewrite function with typesutil.ImportedPkgName.

        Alan Donovan

        Acknowledged

        Line 144, Patchset 4: end = skipWhiteSpaces(pgf, end)
        Alan Donovan . resolved

        Is this necessary? I think it would be fine to replace the selected declarations by "\n" in the source file, and let the formatter tidy up the whitespace on the next save.

        Chiawen Chen

        This is not necessary but nice to have. Otherwise the code action outputs 3 consecutive empty lines in the most common case.

        Alan Donovan

        Ah, because we don't reformat until save. That makes sense. Thanks.

        Line 154, Patchset 2: return &protocol.WorkspaceEdit{
        Alan Donovan . resolved

        In an ideal world the user's cursor would be moved to the new file---otherwise they may wonder what file name was created. That's possible to achieve, but a little tricky: rather than returning a WorkspaceEdit, we would have to make a Client.ApplyEdit downcall, followed by a ShowDocument downcall featuring the new URI. (The Client.ApplyEdit need contain only the file-creation edit; we could return a WorkspaceEdit for the actual changes to the text.)

        Chiawen Chen

        In vscode the CreateFile operation will show the created file in an opened tab (not focused) and shown as unsaved, This should be enough for users to take notice about it. For other editors I have no idea.

        Alan Donovan

        OK, that sounds sufficient.

        Line 154, Patchset 4: parenthesisFreeImports := findParenthesisFreeImports(pgf)
        Alan Donovan . resolved

        I wonder whether this is necessary. If we delete the sole ImportSpec from an import GenDecl, won't the formatter eliminate the declaration entirely? Or does it replace it with import ()? Or "import \n", which doesn't compile?

        In any case, this section needs more commentary.

        Chiawen Chen

        I have added the following comment:

        ```
        // For unparenthesised declarations like `import "fmt"` we remove
        // the whole declaration because simply removing importSpec leaves
        // `import \n`, which does not compile.
        // For parenthesised declarations like `import ("fmt"\n "log")`
        // we only remove the ImportSpec, because removing the whole declaration
        // might remove other ImportsSpecs we don't want to touch.
        ```

        Alan Donovan

        Very nice.

        Line 237, Patchset 4:func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . resolved

        // selectedToplevelDecls returns the lexical extent of the top-level
        // declarations enclosing (start, end), along with the name of the
        // first declaration. It returns zeros on error.
        func selectedToplevelDecls

        Chiawen Chen

        This description does not agree with what I have in my mind:
        1. partial selection of a declaration should not be offered code action.
        2. as an exception selecting only function name should be offered code action.

        I modified the comment to this:
        // selectedToplevelDecls returns the lexical extent of the top-level
        // declarations enclosed by [start, end), along with the name of the
        // first declaration. The returned boolean reports whether the selection
        // should be offered code action.

        Alan Donovan

        Acknowledged

        Line 237, Patchset 4:func findRangeAndFilename(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, error) {
        Alan Donovan . resolved

        You don't need to return an error, since it is ignored in the dryrun mode, and it "can't happen" in the real mode. Just return (0, 0, "") on error, and document this.

        Chiawen Chen

        I changed the error return value to an ok boolean.

        Alan Donovan

        Acknowledged

        Line 238, Patchset 4: if intersect(start, end, pgf.File.Package, pgf.File.Name.End()) {
        return 0, 0, "", errors.New("selection cannot intersect a package declaration")
        }
        Alan Donovan . resolved

        This isn't necessary. If only package decl is selected, the loop below will be a no-op and the function will return an error.

        I think it's fine to be permissive: the only declarations that matter are the ones that start after the imports. If stuff before that is selected, pretend it isn't.

        Chiawen Chen

        I think the main concern of this comment is about the unnecessary use of an error. I changed the error return value to an ok boolean. If you are concerned about that the code action should be offered when package decl is selected, let me know.

        Alan Donovan

        Acknowledged

        Line 251, Patchset 4: if v, ok := node.(*ast.FuncDecl); ok && contain(v.Pos(), v.Name.End(), start, end) {
        Alan Donovan . resolved

        I don't think it's necessary that the name be selected.

        if ok {...}

        Chiawen Chen

        I think you read it in reverse, this reads as "function name contains the selection", not the other way.

        This is useful when a user want to refactor out a function and double click select the function name.

        Alan Donovan

        Indeed. Thanks for clarifying.

        Line 260, Patchset 4: if !contain(start, end, node.Pos(), node.End()) {
        Alan Donovan . unresolved

        Why not? If I select most of two adjacent functions, it's pretty clear what to do.

        Chiawen Chen

        There are two reasons not to offer code action for partial selection:

        1. There is an ambiguity for when there is a type declaration inside of a function body, and only that declaration is selected. A user might want to extract only that type declaration but instead we extract the ambient function.
        Another ambiguity is when a var declaration declared several variables, but only one of them is selected, a user need to guess wether only the selected value specifier is extracted, or the whole variable declaration is extracted.

        2. The user story is that a user want to refactor codes and select the code section intended to move. If a user only selected part of a function, the user often don't want this code action, then offering it is a noise.

        Alan Donovan

        This is really useful information for gopls' documentation (which is rather scant right now but I am working on adding a more complete manual).

        In the meantime, would you mind adding a release note to gopls/doc/release/v0.16.0.md that explains how to use the feature, and any other nuggets of design information (such as your explanation above) that might help users understand? The release note can just be in note form; I'll edit them later.

        Line 277, Patchset 4: if !contain(start, end, node.Pos(), node.End()) {
        return 0, 0, "", errors.New("selection cannot partially intersect a comment")
        }
        Alan Donovan . unresolved

        No need for an error; reduce start to comment.Pos, or increase end to comment.End.

        Chiawen Chen

        When a comment is partially selected, does the user want the comment extracted or not? If we choose to extract it, some users will be surprised, if we chose not to extract it, some others user will be surprised. Not offering code action in this case is a fine way to eliminate this ambiguity.

        Alan Donovan

        I see. This is good information for the release note I just mentioned.

        File gopls/internal/protocol/tsdocument_changes.go
        Line 15, Patchset 6 (Latest):type DocumentChanges struct {
        Alan Donovan . unresolved

        I've extended this to support CreateFile and also DeleteFile in https://go.dev/cl/585277; the change should make it easy for you to use the marker test for create/delete/rename as well. I suggest you rebase once that CL lands.

        File gopls/internal/server/command.go
        Line 885, Patchset 6 (Latest): return nil
        Alan Donovan . unresolved

        We should probably check the Applied flag returned by the client:

        ```
        resp, err := ApplyEdit(...)
        ...
        if !resp.Applied {
        return fmt.Errorf("edits not applied: %s", response.FailureReason)
        }
        ```
        File gopls/internal/test/integration/fake/editor.go
        Line 1417, Patchset 6 (Latest): if change.CreateFile != nil {
        Alan Donovan . unresolved

        (This is also taken care of by https://go.dev/cl/585277.)

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2:func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . unresolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Chiawen Chen

        I tried to write tests in the marker framework, but it does not work for now because the testing infrastructure is missing some implementations around file creation edits. Earlier I decided not to write tests as marker tests because I thought the marker framework cannot express testing of created files (content and filename), but now I think again it probably could.

        Should this be a priority I can try to implement the missing testing infrastructure.

        Alan Donovan

        Got it. I wrote https://go.dev/cl/585277 today which should make it easy to use the marker tests (though I haven't actually tested it). Once that CL lands, why not rebase and try it out?

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 6
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        Gerrit-Comment-Date: Tue, 14 May 2024 20:42:30 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        May 24, 2024, 11:18:02 AM5/24/24
        to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Chiawen Chen

        Alan Donovan added 1 comment

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2:func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . unresolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Chiawen Chen

        I tried to write tests in the marker framework, but it does not work for now because the testing infrastructure is missing some implementations around file creation edits. Earlier I decided not to write tests as marker tests because I thought the marker framework cannot express testing of created files (content and filename), but now I think again it probably could.

        Should this be a priority I can try to implement the missing testing infrastructure.

        Alan Donovan

        Got it. I wrote https://go.dev/cl/585277 today which should make it easy to use the marker tests (though I haven't actually tested it). Once that CL lands, why not rebase and try it out?

        Alan Donovan

        CL 585277 has landed, so you can rebase this CL on top of it. I hope it was helpful.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 6
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        Gerrit-Comment-Date: Fri, 24 May 2024 15:17:56 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        May 29, 2024, 10:20:21 AM5/29/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan and Chiawen Chen

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #7 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 7
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        May 29, 2024, 10:39:05 AM5/29/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 29 comments

        Patchset-level comments
        File-level comment, Patchset 7 (Latest):
        Chiawen Chen . resolved

        Thank you for the review! I have changed the tests to marker tests.

        File gopls/internal/golang/extracttofile.go
        Line 38, Patchset 6:// TODO: handle dot imports
        Alan Donovan . resolved

        We should at least detect dot imports and abort the operation for now; we (or you if you're willing) can add support for them later.

        Chiawen Chen

        I have added the abort handling.

        Line 78, Patchset 6:func ExtractToNewFile(

        ctx context.Context,
        snapshot *cache.Snapshot,
        fh file.Handle,
        rng protocol.Range,
        Alan Donovan . resolved

        Join lines.

        Chiawen Chen

        Done

        Line 96, Patchset 6: start, end, filename, ok := selectedToplevelDecls(pgf, start, end)
        Alan Donovan . resolved

        firstSymbol

        Chiawen Chen

        Done

        Line 108, Patchset 6: adds, deletes := findImportEdits(pgf.File, pkg.GetTypesInfo(), start, end)
        Alan Donovan . resolved

        (The "Get" prefix has since been removed.)

        Chiawen Chen

        Done

        Line 126, Patchset 6: importAdds := ""
        Alan Donovan . resolved

        Let's use a bytes.Buffer here, and start by writing

          fmt.Fprintf(&buf, "package %s\n", pgf.File.Name.Name)

        followed by the imports. Then:
          newFileContent, err := format.Source(buf.Bytes())
        Chiawen Chen

        Done

        Line 154, Patchset 6: return &protocol.WorkspaceEdit{
        Alan Donovan . resolved

        See CL 585277, which should make this slightly easier for you. This should simplify to:

        ```
        edits := append(edits, protocol.TextEdit{Range: replaceRange, NewText: ""})
        return NewWorkspaceEdit(
        protocol.DocumentChangeEdit(fh, edits)
        protocol.DocumentChangeCreate(newFileURI),
        protocol.DocumentChangeEdit(newFileURI, []protocol.TextEdit{{NewText: string(newFileContent)}), nil
        ```

        (You'll need to define protocol.DocumentChangeCreate.)

        Chiawen Chen

        Done

        Line 181, Patchset 6:// resolveNewFileURI checks that basename.go does not exists in dir, otherwise

        // select basename.{1,2,3,4,5}.go as filename.
        Alan Donovan . resolved

        "chooses a new filename in dir, based on the name of the first extracted symbol, and if necessary to disambiguate, a numeric suffix."

        Chiawen Chen

        Done

        Line 183, Patchset 6:func resolveNewFileURI(ctx context.Context, snapshot *cache.Snapshot, dir string, basename string) (protocol.DocumentURI, error) {
        Alan Donovan . resolved

        chooseNewFileURI

        Chiawen Chen

        Done

        Line 183, Patchset 6:func resolveNewFileURI(ctx context.Context, snapshot *cache.Snapshot, dir string, basename string) (protocol.DocumentURI, error) {
        Alan Donovan . resolved

        firstSymbol

        Chiawen Chen

        Done

        Line 184, Patchset 6: basename = strings.ToLower(basename)
        Alan Donovan . resolved

        basename := strings.ToLower(firstSymbol)

        Chiawen Chen

        Done

        Line 189, Patchset 6: return "", nil
        Alan Donovan . resolved

        return "", err // e.g. cancelled

        Chiawen Chen

        Done

        Line 192, Patchset 6: break
        Alan Donovan . resolved

        Change this function to return the non-existent file.Handle on success, and return (fh, nil) here. It will make the calling code more convenient.

        Chiawen Chen

        Done

        Line 194, Patchset 6: if count >= 5 {
        Alan Donovan . resolved

        Change the loop to

             for count := 1; count < 5; count++ {

        and make the final statement
            return nil, fmt.Errorf("resolveNewFileURI: exceeded retry limit")
        Chiawen Chen

        Done

        Line 206, Patchset 6:// should be offered code action.
        Alan Donovan . resolved

        "offered a code action to extract the declarations"

        Chiawen Chen

        Done

        Line 233, Patchset 6: if v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
        Alan Donovan . resolved

        len("type")

        ditto below

        Chiawen Chen

        Done

        Line 282, Patchset 6: i := pos
        Alan Donovan . resolved

        This can be simplified to:

        i := pgf.Tok.Offset(pos)
        rest := pgf.Src[i:]
        return pos + len(rest) - len(bytes.TrimLeft(rest, " \t\n"))

        Then you don't need the skipWhiteSpaces function. (Inline the call.)

        Chiawen Chen

        Done

        Line 308, Patchset 6: rng, _ := pgf.PosRange(node.Pos(), node.End())
        Alan Donovan . resolved

        Don't discard the error here. Use bug.Report if it's supposedly impossible.

        Chiawen Chen

        Done

        Line 318, Patchset 6:func contain(a, b, c, d token.Pos) bool {
        Alan Donovan . resolved

        These verbs should be indicative not imperative, since they are predicates, not actions, so add an 's'. Let's also add a "posRange" prefix since their scope is very large:

        posRangeContains
        posRangeIntersects

        Chiawen Chen

        Done

        File gopls/internal/golang/move_to_new_file.go
        Line 255, Patchset 4: if v, ok := node.(*ast.GenDecl); ok && (v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
        Alan Donovan . resolved

        Again I don't think the contains checks are necessary.

        Chiawen Chen

        Acknowledged

        Line 255, Patchset 4: if v, ok := node.(*ast.GenDecl); ok && (v.Tok == token.TYPE && contain(v.Pos(), v.Pos()+4, start, end) ||
        Alan Donovan . resolved

        Simpler: Tok != types.IMPORT

        Combine with the case for IMPORT:

        case *ast.GenDecl: 
        if Tok == types.IMPORT { return ... }
        ...
        Chiawen Chen

        Acknowledged

        Line 260, Patchset 4: if !contain(start, end, node.Pos(), node.End()) {
        Alan Donovan . resolved

        Why not? If I select most of two adjacent functions, it's pretty clear what to do.

        Chiawen Chen

        There are two reasons not to offer code action for partial selection:

        1. There is an ambiguity for when there is a type declaration inside of a function body, and only that declaration is selected. A user might want to extract only that type declaration but instead we extract the ambient function.
        Another ambiguity is when a var declaration declared several variables, but only one of them is selected, a user need to guess wether only the selected value specifier is extracted, or the whole variable declaration is extracted.

        2. The user story is that a user want to refactor codes and select the code section intended to move. If a user only selected part of a function, the user often don't want this code action, then offering it is a noise.

        Alan Donovan

        This is really useful information for gopls' documentation (which is rather scant right now but I am working on adding a more complete manual).

        In the meantime, would you mind adding a release note to gopls/doc/release/v0.16.0.md that explains how to use the feature, and any other nuggets of design information (such as your explanation above) that might help users understand? The release note can just be in note form; I'll edit them later.

        Chiawen Chen

        Done

        Line 277, Patchset 4: if !contain(start, end, node.Pos(), node.End()) {
        return 0, 0, "", errors.New("selection cannot partially intersect a comment")
        }
        Alan Donovan . resolved

        No need for an error; reduce start to comment.Pos, or increase end to comment.End.

        Chiawen Chen

        When a comment is partially selected, does the user want the comment extracted or not? If we choose to extract it, some users will be surprised, if we chose not to extract it, some others user will be surprised. Not offering code action in this case is a fine way to eliminate this ambiguity.

        Alan Donovan

        I see. This is good information for the release note I just mentioned.

        Chiawen Chen

        Done

        File gopls/internal/protocol/tsdocument_changes.go
        Line 15, Patchset 6:type DocumentChanges struct {
        Alan Donovan . resolved

        I've extended this to support CreateFile and also DeleteFile in https://go.dev/cl/585277; the change should make it easy for you to use the marker test for create/delete/rename as well. I suggest you rebase once that CL lands.

        Chiawen Chen

        Done

        File gopls/internal/server/command.go
        Line 885, Patchset 6: return nil
        Alan Donovan . resolved

        We should probably check the Applied flag returned by the client:

        ```
        resp, err := ApplyEdit(...)
        ...
        if !resp.Applied {
        return fmt.Errorf("edits not applied: %s", response.FailureReason)
        }
        ```
        Chiawen Chen

        Done

        Line 1005, Patchset 7 (Latest): if errors.Is(err, golang.ErrDotImport) {
        showMessage(ctx, c.s.client, protocol.Info, err.Error())
        return nil
        }
        return err
        Chiawen Chen . unresolved

        Not sure if this is a correct implementation to abort the code action.

        File gopls/internal/test/integration/fake/editor.go
        Line 1417, Patchset 6: if change.CreateFile != nil {
        Alan Donovan . resolved

        (This is also taken care of by https://go.dev/cl/585277.)

        Chiawen Chen

        Done

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2:func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . unresolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Chiawen Chen

        I tried to write tests in the marker framework, but it does not work for now because the testing infrastructure is missing some implementations around file creation edits. Earlier I decided not to write tests as marker tests because I thought the marker framework cannot express testing of created files (content and filename), but now I think again it probably could.

        Should this be a priority I can try to implement the missing testing infrastructure.

        Alan Donovan

        Got it. I wrote https://go.dev/cl/585277 today which should make it easy to use the marker tests (though I haven't actually tested it). Once that CL lands, why not rebase and try it out?

        Alan Donovan

        CL 585277 has landed, so you can rebase this CL on top of it. I hope it was helpful.

        Chiawen Chen

        I have changed the tests to marker tests. There are two problem I met:
        1. How do I call `@codeactionedit` with a zero-width location? I need this to test when a user try to use the code action by just placing the cursor on a function name without selecting.
        2. How do I test that for a given selection, the code action is not offered?

        File gopls/internal/test/integration/wrappers.go
        Line 123, Patchset 7 (Latest):func (e *Env) FileContent(name string) string {
        e.T.Helper()
        text, ok := e.Editor.BufferText(name)
        if ok {
        return text
        }
        content, err := e.Sandbox.Workdir.ReadFile(name)
        if err != nil {
        if errors.Is(err, os.ErrNotExist) {
        return ""
        } else {
        e.T.Fatal(err)
        }
        }
        return string(content)
        Chiawen Chen . unresolved

        I have to change this otherwise the marker test fails. Previously `FileContent` panics when the file does not exists, now it returns an empty string. The fail happens when `checkDiffs` calls `FileContent` on a newly created file created by code action, which does not exist on disk or buffer.

        Involved functions are `codeActionEditMarker`, `checkDiffs`, and `FileContent`.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 7
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Wed, 29 May 2024 14:38:56 +0000
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        May 29, 2024, 3:28:22 PM5/29/24
        to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Chiawen Chen

        Alan Donovan added 23 comments

        Patchset-level comments
        Alan Donovan . resolved

        Tests look good; thanks for redoing them.

        Lots of superficial comments, but this is functionally ready to go, I think.

        File gopls/doc/release/v0.16.0.md
        Line 97, Patchset 7 (Latest):which moves selected code sections to a newly created file within the
        Alan Donovan . unresolved

        moves the selected declarations

        Line 98, Patchset 7 (Latest):same package. The created filename is chosen as the first {function, type,
        Alan Donovan . unresolved

        file's name

        Line 99, Patchset 7 (Latest):const, var} name encountered. In addition, import declarations are added or
        Alan Donovan . unresolved

        Import...

        Line 98, Patchset 7 (Latest):same package. The created filename is chosen as the first {function, type,
        const, var} name encountered. In addition, import declarations are added or
        Alan Donovan . unresolved

        based on the first symbol

        Line 102, Patchset 7 (Latest):The user can invoke this code action by selecting a function name, the keywords
        Alan Donovan . unresolved

        "You can find this action by selecting one or more complete declarations, or just the `func`, `const`, `var`, or `type` token of any top-level declaration."

        Line 106, Patchset 7 (Latest):In order to avoid ambiguity and surprise about what to extract, some kinds
        Alan Donovan . unresolved

        I don't think you need to go into such detail; delete this paragraph.

        File gopls/internal/golang/extracttofile.go
        Line 42, Patchset 7 (Latest):func findImportEdits(file *ast.File, info *types.Info, start, end token.Pos) (adds []*ast.ImportSpec, deletes []*ast.ImportSpec, _ error) {
        Alan Donovan . unresolved

        Factor the types: adds, deletes *[]ast.ImportSpec

        Line 57, Patchset 7 (Latest): return nil, nil, ErrDotImport
        Alan Donovan . unresolved

        // TODO: support dot imports.

        Line 99, Patchset 7 (Latest): return nil, bug.Errorf("precondition unmet")
        Alan Donovan . unresolved

        "invalid selection"

        Line 108, Patchset 7 (Latest): return nil, bug.Errorf("findRangeAndFilename returned invalid range: %v", err)
        Alan Donovan . unresolved

        "invalid range: ..."

        Line 165, Patchset 7 (Latest): protocol.DocumentChangeEdit(&uriVersion{uri: newFile.URI(), version: 0}, []protocol.TextEdit{
        Alan Donovan . unresolved

        newFile

        (then delete uriVersion)

        Line 170, Patchset 7 (Latest):// uriVersion is the simplest struct that implements protocol.fileHandle.
        Alan Donovan . unresolved

        Actually, the existing newFile variable is even simpler. :)

        Line 185, Patchset 7 (Latest):func chooseNewFileURI(ctx context.Context, snapshot *cache.Snapshot, dir string, firstSymbol string) (file.Handle, error) {
        Alan Donovan . unresolved

        chooseNewFile

        Line 251, Patchset 7 (Latest): firstName = id.Name
        Alan Donovan . unresolved

        // may be "_"

        (that's ok, I think)

        Line 280, Patchset 7 (Latest):func findParenthesisFreeImports(pgf *parsego.File) map[*ast.ImportSpec]*ast.GenDecl {
        Alan Donovan . unresolved

        // unparenthesizedImports returns a map from each unparenthesized ImportSpec
        // to its enclosing declaration (which may need to be deleted too).
        func unparenthesizedImports

        Line 283, Patchset 7 (Latest): if g, ok := decl.(*ast.GenDecl); ok {
        Alan Donovan . unresolved

        Simplify:

        		if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT && !decl.Lparen.IsValid() {
        decls[decl.Specs[0]] = decl
        }
        Line 296, Patchset 7 (Latest): rng, err := pgf.PosRange(node.Pos(), node.End())
        Alan Donovan . unresolved

        NodeRange(node)

        File gopls/internal/protocol/command/interface.go
        Line 160, Patchset 7 (Latest): ExtractToNewFile(context.Context, ExtractToNewFileArgs) error
        Alan Donovan . unresolved

        Location

        (ExtractToNewFileArgs = URI + Range = Location.)

        File gopls/internal/server/command.go
        Line 1005, Patchset 7 (Latest): if errors.Is(err, golang.ErrDotImport) {
        showMessage(ctx, c.s.client, protocol.Info, err.Error())
        return nil
        }
        return err
        Chiawen Chen . unresolved

        Not sure if this is a correct implementation to abort the code action.

        Alan Donovan

        I think you can just return the error.

        (This will cause one test to fail, so just remove that test.)

        File gopls/internal/test/integration/wrappers.go
        Line 120, Patchset 7 (Latest):// editing session: if the file is open, it returns its buffer content,
        Alan Donovan . unresolved

        // it returns the buffer content for an open file, the
        // on-disk content for an unopened file,
        // or "" for a non-existent file.

        Line 123, Patchset 7 (Latest):func (e *Env) FileContent(name string) string {
        e.T.Helper()
        text, ok := e.Editor.BufferText(name)
        if ok {
        return text
        }
        content, err := e.Sandbox.Workdir.ReadFile(name)
        if err != nil {
        if errors.Is(err, os.ErrNotExist) {
        return ""
        } else {
        e.T.Fatal(err)
        }
        }
        return string(content)
        Chiawen Chen . resolved

        I have to change this otherwise the marker test fails. Previously `FileContent` panics when the file does not exists, now it returns an empty string. The fail happens when `checkDiffs` calls `FileContent` on a newly created file created by code action, which does not exist on disk or buffer.

        Involved functions are `codeActionEditMarker`, `checkDiffs`, and `FileContent`.

        Alan Donovan

        I think this is fine; its sole use is from checkDiffs.

        File gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
        Line 74, Patchset 7 (Latest):-- dot_import.go --
        // This case is not yet implemented, it aborts and shows a message to the client.
        package main
        import . "fmt"
        func F() { Println() } //@codeactionedit("func", "refactor.extract", dot_import)

        Alan Donovan . unresolved

        Let's leave this out for now (you can make a note at the top to add it back later).

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 7
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        Gerrit-Comment-Date: Wed, 29 May 2024 19:28:17 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Chiawen Chen <gol...@gmail.com>
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        May 31, 2024, 1:37:01 AM5/31/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan and Chiawen Chen

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #8 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 8
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        May 31, 2024, 1:38:01 AM5/31/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 22 comments

        Patchset-level comments
        File-level comment, Patchset 7:
        Chiawen Chen . resolved

        Thanks for the review!

        File gopls/doc/release/v0.16.0.md
        Line 97, Patchset 7:which moves selected code sections to a newly created file within the
        Alan Donovan . resolved

        moves the selected declarations

        Chiawen Chen

        Done

        Line 98, Patchset 7:same package. The created filename is chosen as the first {function, type,
        Alan Donovan . resolved

        file's name

        Chiawen Chen

        Done

        Line 98, Patchset 7:same package. The created filename is chosen as the first {function, type,
        const, var} name encountered. In addition, import declarations are added or
        Alan Donovan . resolved

        based on the first symbol

        Chiawen Chen

        Done

        Line 99, Patchset 7:const, var} name encountered. In addition, import declarations are added or
        Alan Donovan . resolved

        Import...

        Chiawen Chen

        Done

        Line 102, Patchset 7:The user can invoke this code action by selecting a function name, the keywords
        Alan Donovan . resolved

        "You can find this action by selecting one or more complete declarations, or just the `func`, `const`, `var`, or `type` token of any top-level declaration."

        Chiawen Chen

        Done

        Line 106, Patchset 7:In order to avoid ambiguity and surprise about what to extract, some kinds
        Alan Donovan . resolved

        I don't think you need to go into such detail; delete this paragraph.

        Chiawen Chen

        Done

        File gopls/internal/golang/extracttofile.go
        Line 42, Patchset 7:func findImportEdits(file *ast.File, info *types.Info, start, end token.Pos) (adds []*ast.ImportSpec, deletes []*ast.ImportSpec, _ error) {
        Alan Donovan . resolved

        Factor the types: adds, deletes *[]ast.ImportSpec

        Chiawen Chen

        Done

        Line 57, Patchset 7: return nil, nil, ErrDotImport
        Alan Donovan . resolved

        // TODO: support dot imports.

        Chiawen Chen

        Done

        Line 99, Patchset 7: return nil, bug.Errorf("precondition unmet")
        Alan Donovan . resolved

        "invalid selection"

        Chiawen Chen

        Done

        Line 108, Patchset 7: return nil, bug.Errorf("findRangeAndFilename returned invalid range: %v", err)
        Alan Donovan . resolved

        "invalid range: ..."

        Chiawen Chen

        Done

        Line 165, Patchset 7: protocol.DocumentChangeEdit(&uriVersion{uri: newFile.URI(), version: 0}, []protocol.TextEdit{
        Alan Donovan . resolved

        newFile

        (then delete uriVersion)

        Chiawen Chen

        Done

        Line 170, Patchset 7:// uriVersion is the simplest struct that implements protocol.fileHandle.
        Alan Donovan . resolved

        Actually, the existing newFile variable is even simpler. :)

        Chiawen Chen

        :)

        Line 185, Patchset 7:func chooseNewFileURI(ctx context.Context, snapshot *cache.Snapshot, dir string, firstSymbol string) (file.Handle, error) {
        Alan Donovan . resolved

        chooseNewFile

        Chiawen Chen

        Done

        Line 251, Patchset 7: firstName = id.Name
        Alan Donovan . resolved

        // may be "_"

        (that's ok, I think)

        Chiawen Chen

        Done

        Line 280, Patchset 7:func findParenthesisFreeImports(pgf *parsego.File) map[*ast.ImportSpec]*ast.GenDecl {
        Alan Donovan . resolved

        // unparenthesizedImports returns a map from each unparenthesized ImportSpec
        // to its enclosing declaration (which may need to be deleted too).
        func unparenthesizedImports

        Chiawen Chen

        Done

        Line 283, Patchset 7: if g, ok := decl.(*ast.GenDecl); ok {
        Alan Donovan . resolved

        Simplify:

        		if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT && !decl.Lparen.IsValid() {
        decls[decl.Specs[0]] = decl
        }
        Chiawen Chen

        Done

        Line 296, Patchset 7: rng, err := pgf.PosRange(node.Pos(), node.End())
        Alan Donovan . resolved

        NodeRange(node)

        Chiawen Chen

        Done

        File gopls/internal/protocol/command/interface.go
        Line 160, Patchset 7: ExtractToNewFile(context.Context, ExtractToNewFileArgs) error
        Alan Donovan . resolved

        Location

        (ExtractToNewFileArgs = URI + Range = Location.)

        Chiawen Chen

        Done

        File gopls/internal/server/command.go
        Line 1005, Patchset 7: if errors.Is(err, golang.ErrDotImport) {

        showMessage(ctx, c.s.client, protocol.Info, err.Error())
        return nil
        }
        return err
        Chiawen Chen . resolved

        Not sure if this is a correct implementation to abort the code action.

        Alan Donovan

        I think you can just return the error.

        (This will cause one test to fail, so just remove that test.)

        Chiawen Chen

        Done

        File gopls/internal/test/integration/wrappers.go
        Line 120, Patchset 7:// editing session: if the file is open, it returns its buffer content,
        Alan Donovan . resolved

        // it returns the buffer content for an open file, the
        // on-disk content for an unopened file,
        // or "" for a non-existent file.

        Chiawen Chen

        Done

        File gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
        Line 74, Patchset 7:-- dot_import.go --

        // This case is not yet implemented, it aborts and shows a message to the client.
        package main
        import . "fmt"
        func F() { Println() } //@codeactionedit("func", "refactor.extract", dot_import)

        Alan Donovan . resolved

        Let's leave this out for now (you can make a note at the top to add it back later).

        Chiawen Chen

        Done

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 7
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Fri, 31 May 2024 05:37:54 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        May 31, 2024, 4:21:01 PM5/31/24
        to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Chiawen Chen

        Alan Donovan added 1 comment

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2:func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . unresolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Chiawen Chen

        I tried to write tests in the marker framework, but it does not work for now because the testing infrastructure is missing some implementations around file creation edits. Earlier I decided not to write tests as marker tests because I thought the marker framework cannot express testing of created files (content and filename), but now I think again it probably could.

        Should this be a priority I can try to implement the missing testing infrastructure.

        Alan Donovan

        Got it. I wrote https://go.dev/cl/585277 today which should make it easy to use the marker tests (though I haven't actually tested it). Once that CL lands, why not rebase and try it out?

        Alan Donovan

        CL 585277 has landed, so you can rebase this CL on top of it. I hope it was helpful.

        Chiawen Chen

        I have changed the tests to marker tests. There are two problem I met:
        1. How do I call `@codeactionedit` with a zero-width location? I need this to test when a user try to use the code action by just placing the cursor on a function name without selecting.
        2. How do I test that for a given selection, the code action is not offered?

        Alan Donovan

        How do I call @codeactionedit with a zero-width location?

        If the regular expression contains a submatch groups, the position of the group is what matters. So you can say "(a)b" to match the region of the letter "a" but only in a context preceded by "b". Thus: "()b" means the zero-width space before the b.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 8
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-Attention: Chiawen Chen <gol...@gmail.com>
        Gerrit-Comment-Date: Fri, 31 May 2024 20:20:56 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        unsatisfied_requirement
        open
        diffy

        Gerrit Bot (Gerrit)

        unread,
        Jun 18, 2024, 7:08:17 AM6/18/24
        to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Alan Donovan and Chiawen Chen

        Gerrit Bot uploaded new patchset

        Gerrit Bot uploaded patch set #9 to this change.
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        • Chiawen Chen
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
        Gerrit-Change-Number: 565895
        Gerrit-PatchSet: 9
        Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-CC: Chiawen Chen <gol...@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        unsatisfied_requirement
        open
        diffy

        Chiawen Chen (Gerrit)

        unread,
        Jun 18, 2024, 7:08:39 AM6/18/24
        to Gerrit Bot, goph...@pubsubhelper.golang.org, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Chiawen Chen added 2 comments

        Patchset-level comments
        File-level comment, Patchset 8:
        Chiawen Chen . resolved

        I rebased and added some tests.

        File gopls/internal/test/integration/misc/move_to_new_file_test.go
        Line 29, Patchset 2:func compileTemplate(env *Env, text string, dest string) protocol.Location {
        Alan Donovan . resolved

        Neat idea! But we usually write our tests in the marker framework, in the form of a standardized marker/testdata/*.txt file. It's possible to express a whole sequence of test cases in a single file; or you can create several files for different groups of tests if you prefer.

        One benefit of the marker approach is that you can edit the input file and re-run the test without recompiling anything at all. Another is uniformity of the tests.

        Have you tried expressing these tests in that form?

        Chiawen Chen

        I tried to write tests in the marker framework, but it does not work for now because the testing infrastructure is missing some implementations around file creation edits. Earlier I decided not to write tests as marker tests because I thought the marker framework cannot express testing of created files (content and filename), but now I think again it probably could.

        Should this be a priority I can try to implement the missing testing infrastructure.

        Alan Donovan

        Got it. I wrote https://go.dev/cl/585277 today which should make it easy to use the marker tests (though I haven't actually tested it). Once that CL lands, why not rebase and try it out?

        Alan Donovan

        CL 585277 has landed, so you can rebase this CL on top of it. I hope it was helpful.

        Chiawen Chen

        I have changed the tests to marker tests. There are two problem I met:
        1. How do I call `@codeactionedit` with a zero-width location? I need this to test when a user try to use the code action by just placing the cursor on a function name without selecting.
        2. How do I test that for a given selection, the code action is not offered?

        Alan Donovan

        How do I call @codeactionedit with a zero-width location?

        If the regular expression contains a submatch groups, the position of the group is what matters. So you can say "(a)b" to match the region of the letter "a" but only in a context preceded by "b". Thus: "()b" means the zero-width space before the b.

        Chiawen Chen

        Thanks I have added the tests.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
          • requirement is not satisfiedCode-Review
          • requirement satisfiedNo-Unresolved-Comments
          • requirement is not satisfiedReview-Enforcement
          • requirement is not satisfiedTryBots-Pass
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: tools
          Gerrit-Branch: master
          Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
          Gerrit-Change-Number: 565895
          Gerrit-PatchSet: 8
          Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
          Gerrit-Reviewer: Alan Donovan <adon...@google.com>
          Gerrit-CC: Chiawen Chen <gol...@gmail.com>
          Gerrit-CC: Gopher Robot <go...@golang.org>
          Gerrit-Attention: Alan Donovan <adon...@google.com>
          Gerrit-Comment-Date: Tue, 18 Jun 2024 11:08:30 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: No
          unsatisfied_requirement
          satisfied_requirement
          open
          diffy

          Alan Donovan (Gerrit)

          unread,
          Jun 18, 2024, 11:15:44 AM6/18/24
          to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com

          Alan Donovan voted and added 1 comment

          Votes added by Alan Donovan

          Commit-Queue+1

          1 comment

          Patchset-level comments
          File-level comment, Patchset 9 (Latest):
          Alan Donovan . resolved

          LGTM. Many thanks again.

          Rob, could you +1?

          Open in Gerrit

          Related details

          Attention set is empty
          Submit Requirements:
          • requirement is not satisfiedCode-Review
          • requirement satisfiedNo-Unresolved-Comments
          • requirement is not satisfiedReview-Enforcement
          • requirement is not satisfiedTryBots-Pass
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: tools
          Gerrit-Branch: master
          Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
          Gerrit-Change-Number: 565895
          Gerrit-PatchSet: 9
          Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
          Gerrit-Reviewer: Alan Donovan <adon...@google.com>
          Gerrit-CC: Chiawen Chen <gol...@gmail.com>
          Gerrit-CC: Gopher Robot <go...@golang.org>
          Gerrit-Comment-Date: Tue, 18 Jun 2024 15:15:38 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: Yes
          unsatisfied_requirement
          satisfied_requirement
          open
          diffy

          Alan Donovan (Gerrit)

          unread,
          Jun 18, 2024, 12:20:30 PM6/18/24
          to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
          Attention needed from Robert Findley

          Alan Donovan added 3 comments

          Patchset-level comments
          Alan Donovan . resolved

          Two tests are failing but the fixes are simple; see below.

          File gopls/internal/golang/extracttofile.go
          Line 102, Patchset 9 (Latest): rest := pgf.Src[pgf.Tok.Offset(end):]
          Alan Donovan . unresolved

          This should use safetoken instead of calling token.File.Offset directly:

          ```
          offset, err := safetoken.Offset(pgf.Tok, end)

          if err != nil {
          return nil, err
          }
                 rest := pgf.Src[offset:]
          ```
          File gopls/internal/protocol/command/command_gen.go
          Line 78, Patchset 9 (Latest): ExtractToNewFile,
          Alan Donovan . unresolved

          Could you run (cd gopls && go generate ./...) so that the API and docs pick up the new command?

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Robert Findley
          Submit Requirements:
            • requirement is not satisfiedCode-Review
            • requirement is not satisfiedNo-Unresolved-Comments
            • requirement is not satisfiedReview-Enforcement
            • requirement is not satisfiedTryBots-Pass
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: comment
            Gerrit-Project: tools
            Gerrit-Branch: master
            Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
            Gerrit-Change-Number: 565895
            Gerrit-PatchSet: 9
            Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
            Gerrit-Reviewer: Alan Donovan <adon...@google.com>
            Gerrit-CC: Chiawen Chen <gol...@gmail.com>
            Gerrit-CC: Gopher Robot <go...@golang.org>
            Gerrit-CC: Robert Findley <rfin...@google.com>
            Gerrit-Attention: Robert Findley <rfin...@google.com>
            Gerrit-Comment-Date: Tue, 18 Jun 2024 16:20:22 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            unsatisfied_requirement
            open
            diffy

            Gerrit Bot (Gerrit)

            unread,
            Jun 18, 2024, 7:13:08 PM6/18/24
            to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
            Attention needed from Alan Donovan and Robert Findley

            Gerrit Bot uploaded new patchset

            Gerrit Bot uploaded patch set #10 to this change.
            Following approvals got outdated and were removed:
            • TryBots-Pass: LUCI-TryBot-Result-1 by Go LUCI
            Open in Gerrit

            Related details

            Attention is currently required from:
            • Alan Donovan
            • Robert Findley
            Submit Requirements:
            • requirement is not satisfiedCode-Review
            • requirement is not satisfiedNo-Unresolved-Comments
            • requirement is not satisfiedReview-Enforcement
            • requirement is not satisfiedTryBots-Pass
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: newpatchset
            Gerrit-Project: tools
            Gerrit-Branch: master
            Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
            Gerrit-Change-Number: 565895
            Gerrit-PatchSet: 10
            Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
            Gerrit-Reviewer: Alan Donovan <adon...@google.com>
            Gerrit-CC: Chiawen Chen <gol...@gmail.com>
            Gerrit-CC: Gopher Robot <go...@golang.org>
            Gerrit-CC: Robert Findley <rfin...@google.com>
            Gerrit-Attention: Robert Findley <rfin...@google.com>
            Gerrit-Attention: Alan Donovan <adon...@google.com>
            unsatisfied_requirement
            open
            diffy

            Chiawen Chen (Gerrit)

            unread,
            Jun 18, 2024, 7:14:56 PM6/18/24
            to Gerrit Bot, goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
            Attention needed from Alan Donovan and Robert Findley

            Chiawen Chen added 3 comments

            Patchset-level comments
            File-level comment, Patchset 10 (Latest):
            Chiawen Chen . resolved

            The two problems are fixed.

            File gopls/internal/golang/extracttofile.go
            Line 102, Patchset 9: rest := pgf.Src[pgf.Tok.Offset(end):]
            Alan Donovan . resolved

            This should use safetoken instead of calling token.File.Offset directly:

            ```
            offset, err := safetoken.Offset(pgf.Tok, end)
            if err != nil {
            return nil, err
            }
            rest := pgf.Src[offset:]
            ```
            Chiawen Chen

            Done

            File gopls/internal/protocol/command/command_gen.go
            Line 78, Patchset 9: ExtractToNewFile,
            Alan Donovan . resolved

            Could you run (cd gopls && go generate ./...) so that the API and docs pick up the new command?

            Chiawen Chen

            Done

            Open in Gerrit

            Related details

            Attention is currently required from:
            • Alan Donovan
            • Robert Findley
            Submit Requirements:
              • requirement is not satisfiedCode-Review
              • requirement satisfiedNo-Unresolved-Comments
              • requirement is not satisfiedReview-Enforcement
              • requirement is not satisfiedTryBots-Pass
              Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
              Gerrit-MessageType: comment
              Gerrit-Project: tools
              Gerrit-Branch: master
              Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
              Gerrit-Change-Number: 565895
              Gerrit-PatchSet: 10
              Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
              Gerrit-Reviewer: Alan Donovan <adon...@google.com>
              Gerrit-CC: Chiawen Chen <gol...@gmail.com>
              Gerrit-CC: Gopher Robot <go...@golang.org>
              Gerrit-CC: Robert Findley <rfin...@google.com>
              Gerrit-Attention: Robert Findley <rfin...@google.com>
              Gerrit-Attention: Alan Donovan <adon...@google.com>
              Gerrit-Comment-Date: Tue, 18 Jun 2024 23:14:47 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: No
              Comment-In-Reply-To: Alan Donovan <adon...@google.com>
              unsatisfied_requirement
              satisfied_requirement
              open
              diffy

              Alan Donovan (Gerrit)

              unread,
              Jun 18, 2024, 7:15:59 PM6/18/24
              to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
              Attention needed from Robert Findley

              Alan Donovan voted Commit-Queue+1

              Commit-Queue+1
              Open in Gerrit

              Related details

              Attention is currently required from:
              • Robert Findley
              Submit Requirements:
              • requirement is not satisfiedCode-Review
              • requirement satisfiedNo-Unresolved-Comments
              • requirement is not satisfiedReview-Enforcement
              • requirement is not satisfiedTryBots-Pass
              Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
              Gerrit-MessageType: comment
              Gerrit-Project: tools
              Gerrit-Branch: master
              Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
              Gerrit-Change-Number: 565895
              Gerrit-PatchSet: 10
              Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
              Gerrit-Reviewer: Alan Donovan <adon...@google.com>
              Gerrit-CC: Chiawen Chen <gol...@gmail.com>
              Gerrit-CC: Gopher Robot <go...@golang.org>
              Gerrit-CC: Robert Findley <rfin...@google.com>
              Gerrit-Attention: Robert Findley <rfin...@google.com>
              Gerrit-Comment-Date: Tue, 18 Jun 2024 23:15:54 +0000
              Gerrit-HasComments: No
              Gerrit-Has-Labels: Yes
              unsatisfied_requirement
              satisfied_requirement
              open
              diffy

              Alan Donovan (Gerrit)

              unread,
              Jun 18, 2024, 7:25:55 PM6/18/24
              to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
              Attention needed from Robert Findley

              Alan Donovan added 1 comment

              Patchset-level comments
              Alan Donovan . resolved

              One final build failure, with go1.19:

              internal/golang/extracttofile.go:155:35: pgf.File.FileStart undefined (type *ast.File has no field or method FileStart)

              Since this CL isn't going to make it into gopls/v0.16.0, I suggest you just wait for a few days till we drop support for building gopls with go1.{19,20,22,22}, (about which we are unreasonably excited).

              Gerrit-Comment-Date: Tue, 18 Jun 2024 23:25:50 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: No
              unsatisfied_requirement
              satisfied_requirement
              open
              diffy

              Alan Donovan (Gerrit)

              unread,
              Jun 25, 2024, 10:46:21 AM6/25/24
              to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com

              Alan Donovan added 2 comments

              File gopls/doc/release/v0.16.0.md
              Line 173, Patchset 10 (Latest):### Extract declarations to new file
              Alan Donovan . unresolved

              Since the v0.16.0 release went out last week, this should go in a v0.17.0.md file (create it as needed).

              File gopls/internal/golang/extracttofile.go
              Line 155, Patchset 10 (Latest): buf.Write(pgf.Src[start-pgf.File.FileStart : end-pgf.File.FileStart])
              Alan Donovan . unresolved

              Rather than keep waiting for changes on our side, let's make this small patch and get this CL merged:

              	fileStart := pgf.Tok.Pos(0) // TODO(adonovan): use go1.20 pgf.File.FileStart
              buf.Write(pgf.Src[start-fileStart : end-fileStart])
              Open in Gerrit

              Related details

              Attention set is empty
              Submit Requirements:
                • requirement is not satisfiedCode-Review
                • requirement is not satisfiedNo-Unresolved-Comments
                • requirement is not satisfiedReview-Enforcement
                • requirement is not satisfiedTryBots-Pass
                Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                Gerrit-MessageType: comment
                Gerrit-Project: tools
                Gerrit-Branch: master
                Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                Gerrit-Change-Number: 565895
                Gerrit-PatchSet: 10
                Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
                Gerrit-Reviewer: Alan Donovan <adon...@google.com>
                Gerrit-CC: Chiawen Chen <gol...@gmail.com>
                Gerrit-CC: Gopher Robot <go...@golang.org>
                Gerrit-CC: Robert Findley <rfin...@google.com>
                Gerrit-Comment-Date: Tue, 25 Jun 2024 14:46:16 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                unsatisfied_requirement
                open
                diffy

                Gerrit Bot (Gerrit)

                unread,
                Jun 25, 2024, 9:25:10 PM6/25/24
                to Chiawen Chen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
                Attention needed from Alan Donovan

                Gerrit Bot uploaded new patchset

                Gerrit Bot uploaded patch set #11 to this change.
                Following approvals got outdated and were removed:
                • TryBots-Pass: LUCI-TryBot-Result-1 by Go LUCI
                Open in Gerrit

                Related details

                Attention is currently required from:
                • Alan Donovan
                Submit Requirements:
                • requirement is not satisfiedCode-Review
                • requirement is not satisfiedNo-Unresolved-Comments
                • requirement is not satisfiedReview-Enforcement
                • requirement is not satisfiedTryBots-Pass
                Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                Gerrit-MessageType: newpatchset
                Gerrit-Project: tools
                Gerrit-Branch: master
                Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                Gerrit-Change-Number: 565895
                Gerrit-PatchSet: 11
                Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
                Gerrit-Reviewer: Alan Donovan <adon...@google.com>
                Gerrit-CC: Chiawen Chen <gol...@gmail.com>
                Gerrit-CC: Gopher Robot <go...@golang.org>
                Gerrit-CC: Robert Findley <rfin...@google.com>
                Gerrit-Attention: Alan Donovan <adon...@google.com>
                unsatisfied_requirement
                open
                diffy

                Chiawen Chen (Gerrit)

                unread,
                Jun 25, 2024, 9:47:15 PM6/25/24
                to Gerrit Bot, goph...@pubsubhelper.golang.org, Go LUCI, Alan Donovan, Robert Findley, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com
                Attention needed from Alan Donovan

                Chiawen Chen added 2 comments

                File gopls/doc/release/v0.16.0.md
                Line 173, Patchset 10:### Extract declarations to new file
                Alan Donovan . resolved

                Since the v0.16.0 release went out last week, this should go in a v0.17.0.md file (create it as needed).

                Chiawen Chen

                Done

                File gopls/internal/golang/extracttofile.go
                Line 155, Patchset 10: buf.Write(pgf.Src[start-pgf.File.FileStart : end-pgf.File.FileStart])
                Alan Donovan . resolved

                Rather than keep waiting for changes on our side, let's make this small patch and get this CL merged:

                	fileStart := pgf.Tok.Pos(0) // TODO(adonovan): use go1.20 pgf.File.FileStart
                buf.Write(pgf.Src[start-fileStart : end-fileStart])
                Chiawen Chen

                Done

                Open in Gerrit

                Related details

                Attention is currently required from:
                • Alan Donovan
                Submit Requirements:
                  • requirement is not satisfiedCode-Review
                  • requirement satisfiedNo-Unresolved-Comments
                  • requirement is not satisfiedReview-Enforcement
                  • requirement is not satisfiedTryBots-Pass
                  Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                  Gerrit-MessageType: comment
                  Gerrit-Project: tools
                  Gerrit-Branch: master
                  Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                  Gerrit-Change-Number: 565895
                  Gerrit-PatchSet: 11
                  Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
                  Gerrit-Reviewer: Alan Donovan <adon...@google.com>
                  Gerrit-CC: Chiawen Chen <gol...@gmail.com>
                  Gerrit-CC: Gopher Robot <go...@golang.org>
                  Gerrit-CC: Robert Findley <rfin...@google.com>
                  Gerrit-Attention: Alan Donovan <adon...@google.com>
                  Gerrit-Comment-Date: Wed, 26 Jun 2024 01:47:06 +0000
                  Gerrit-HasComments: Yes
                  Gerrit-Has-Labels: No
                  Comment-In-Reply-To: Alan Donovan <adon...@google.com>
                  unsatisfied_requirement
                  satisfied_requirement
                  open
                  diffy

                  Alan Donovan (Gerrit)

                  unread,
                  Jun 26, 2024, 9:31:35 AM6/26/24
                  to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com

                  Alan Donovan voted and added 1 comment

                  Votes added by Alan Donovan

                  Auto-Submit+1
                  Code-Review+2
                  Commit-Queue+1

                  1 comment

                  File gopls/doc/release/v0.17.0.md
                  Line 24, Patchset 11 (Latest):of paritial selection of a declration cannot invoke this code action.
                  Alan Donovan . resolved

                  I'll fix these typos when I next edit the draft release notes. Let's get this CL in.

                  Open in Gerrit

                  Related details

                  Attention set is empty
                  Submit Requirements:
                  • requirement satisfiedCode-Review
                  • requirement satisfiedNo-Unresolved-Comments
                  • requirement is not satisfiedReview-Enforcement
                  • requirement is not satisfiedTryBots-Pass
                  Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                  Gerrit-MessageType: comment
                  Gerrit-Project: tools
                  Gerrit-Branch: master
                  Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                  Gerrit-Change-Number: 565895
                  Gerrit-PatchSet: 11
                  Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
                  Gerrit-Reviewer: Alan Donovan <adon...@google.com>
                  Gerrit-CC: Chiawen Chen <gol...@gmail.com>
                  Gerrit-CC: Gopher Robot <go...@golang.org>
                  Gerrit-CC: Robert Findley <rfin...@google.com>
                  Gerrit-Comment-Date: Wed, 26 Jun 2024 13:31:28 +0000
                  Gerrit-HasComments: Yes
                  Gerrit-Has-Labels: Yes
                  satisfied_requirement
                  unsatisfied_requirement
                  open
                  diffy

                  Robert Findley (Gerrit)

                  unread,
                  Jun 26, 2024, 10:06:03 AM6/26/24
                  to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, Go LUCI, Alan Donovan, triciu...@appspot.gserviceaccount.com, Gopher Robot, golang-co...@googlegroups.com

                  Robert Findley voted Code-Review+1

                  Code-Review+1
                  Open in Gerrit

                  Related details

                  Attention set is empty
                  Submit Requirements:
                    • requirement satisfiedCode-Review
                    • requirement satisfiedNo-Unresolved-Comments
                    • requirement satisfiedReview-Enforcement
                    • requirement satisfiedTryBots-Pass
                    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                    Gerrit-MessageType: comment
                    Gerrit-Project: tools
                    Gerrit-Branch: master
                    Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                    Gerrit-Change-Number: 565895
                    Gerrit-PatchSet: 11
                    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
                    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
                    Gerrit-Reviewer: Robert Findley <rfin...@google.com>
                    Gerrit-CC: Chiawen Chen <gol...@gmail.com>
                    Gerrit-CC: Gopher Robot <go...@golang.org>
                    Gerrit-Comment-Date: Wed, 26 Jun 2024 14:05:57 +0000
                    Gerrit-HasComments: No
                    Gerrit-Has-Labels: Yes
                    satisfied_requirement
                    open
                    diffy

                    Gopher Robot (Gerrit)

                    unread,
                    Jun 26, 2024, 10:07:09 AM6/26/24
                    to Gerrit Bot, Chiawen Chen, goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Robert Findley, Go LUCI, Alan Donovan, triciu...@appspot.gserviceaccount.com, golang-co...@googlegroups.com

                    Gopher Robot submitted the change

                    Change information

                    Commit message:
                    gopls/internal: add code action "extract declarations to new file"

                    This code action moves selected code sections to a newly created file within the same package. The created filename is chosen as the first {function, type, const, var} name encountered. In addition, import declarations are added or removed as needed.

                    Fixes golang/go#65707
                    Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                    GitHub-Last-Rev: e551a8a24f3dc5ea51e1c54f9f28c147c7669b11
                    GitHub-Pull-Request: golang/tools#479
                    Auto-Submit: Alan Donovan <adon...@google.com>
                    Reviewed-by: Robert Findley <rfin...@google.com>
                    Reviewed-by: Alan Donovan <adon...@google.com>
                    Files:
                    • M gopls/doc/commands.md
                    • M gopls/doc/release/v0.17.0.md
                    • M gopls/internal/doc/api.json
                    • M gopls/internal/golang/codeaction.go
                    • A gopls/internal/golang/extracttofile.go
                    • M gopls/internal/protocol/command/command_gen.go
                    • M gopls/internal/protocol/command/interface.go
                    • M gopls/internal/protocol/edits.go
                    • M gopls/internal/server/command.go
                    • M gopls/internal/test/integration/wrappers.go
                    • A gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
                    Change size: L
                    Delta: 11 files changed, 706 insertions(+), 8 deletions(-)
                    Branch: refs/heads/master
                    Submit Requirements:
                    • requirement satisfiedCode-Review: +2 by Alan Donovan, +1 by Robert Findley
                    • requirement satisfiedTryBots-Pass: LUCI-TryBot-Result+1 by Go LUCI
                    Open in Gerrit
                    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                    Gerrit-MessageType: merged
                    Gerrit-Project: tools
                    Gerrit-Branch: master
                    Gerrit-Change-Id: I3fd45afd3569e4e0cee17798a48bde6916eb57b8
                    Gerrit-Change-Number: 565895
                    Gerrit-PatchSet: 12
                    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
                    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
                    Gerrit-Reviewer: Gopher Robot <go...@golang.org>
                    Gerrit-Reviewer: Robert Findley <rfin...@google.com>
                    Gerrit-CC: Chiawen Chen <gol...@gmail.com>
                    open
                    diffy
                    satisfied_requirement
                    Reply all
                    Reply to author
                    Forward
                    0 new messages