blob: 738aa0faaa0a7c6eb3e08f5af108126b9568f441 [file] [log] [blame]
// Copyright 2025 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 analysisinternal_test
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"slices"
"testing"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/diff"
)
func TestDeleteVar(t *testing.T) {
// Each example deletes var v.
for i, test := range []struct {
src string
want string
}{
// package-level GenDecl > ValueSpec
{
"package p; var v int",
"package p; ",
},
{
"package p; var x, v int",
"package p; var x int",
},
{
"package p; var v, x int",
"package p; var x int",
},
{
"package p; var ( v int )",
"package p;",
},
{
"package p; var ( x, v int )",
"package p; var ( x int )",
},
{
"package p; var ( v, x int )",
"package p; var ( x int )",
},
{
"package p; var v, x = 1, 2",
"package p; var x = 2",
},
{
"package p; var x, v = 1, 2",
"package p; var x = 1",
},
{
"package p; var v, x = fx(), fx()",
"package p; var _, x = fx(), fx()",
},
{
"package p; var v, _ = fx(), fx()",
"package p; var _, _ = fx(), fx()",
},
{
"package p; var _, v = fx(), fx()",
"package p; var _, _ = fx(), fx()",
},
{
"package p; var v = fx()",
"package p; var _ = fx()",
},
{
"package p; var ( a int; v int; c int )",
"package p; var ( a int; c int )",
},
{
"package p; var ( a int; v int = 2; c int )",
"package p; var ( a int; c int )",
},
// GenDecl doc comments are not deleted unless decl is deleted.
{
"package p\n// comment\nvar ( v int )",
"package p",
},
{
"package p\n// comment\nvar v int",
"package p",
},
{
"package p\n/* comment */\nvar v int",
"package p",
},
{
"package p\n// comment\nvar ( v, x int )",
"package p\n// comment\nvar ( x int )",
},
{
"package p\n// comment\nvar v, x int",
"package p\n// comment\nvar x int",
},
{
"package p\n/* comment */\nvar x, v int",
"package p\n/* comment */\nvar x int",
},
// ValueSpec leading doc comments
{
"package p\nvar (\n// comment\nv int; x int )",
"package p\nvar (\nx int )",
},
{
"package p\nvar (\n// comment\nx int; v int )",
"package p\nvar (\n// comment\nx int )",
},
// ValueSpec trailing line comments
{
"package p; var ( v int // comment\nx int )",
"package p; var ( x int )",
},
{
"package p; var ( x int // comment\nv int )",
"package p; var ( x int // comment\n )",
},
{
"package p; var ( v int /* comment */)",
"package p;",
},
{
"package p; var ( v int // comment\n)",
"package p;",
},
{
"package p; var ( v int ) // comment",
"package p;",
},
{
"package p; var ( x, v int /* comment */ )",
"package p; var ( x int /* comment */ )",
},
{
"package p; var ( v, x int /* comment */ )",
"package p; var ( x int /* comment */ )",
},
{
"package p; var ( x, v int // comment\n)",
"package p; var ( x int // comment\n)",
},
{
"package p; var ( v, x int // comment\n)",
"package p; var ( x int // comment\n)",
},
{
"package p; var ( v, x int ) // comment",
"package p; var ( x int ) // comment",
},
{
"package p; var ( x int; v int // comment\n)",
"package p; var ( x int )",
},
{
"package p; var ( v int // comment\n x int )",
"package p; var ( x int )",
},
// local DeclStmt > GenDecl > ValueSpec
// (The only interesting cases
// here are the total deletions.)
{
"package p; func _() { var v int }",
"package p; func _() {}",
},
{
"package p; func _() { var ( v int ) }",
"package p; func _() {}",
},
{
"package p; func _() { var ( v int // comment\n) }",
"package p; func _() {}",
},
// TODO(adonovan,pjw): change DeleteStmt's trailing comment handling.
// {
// "package p; func _() { var ( v int ) // comment\n }",
// "package p; func _() {}",
// },
// {
// "package p; func _() { var v int // comment\n }",
// "package p; func _() {}",
// },
// AssignStmt
{
"package p; func _() { v := 0 }",
"package p; func _() {}",
},
{
"package p; func _() { x, v := 0, 1 }",
"package p; func _() { x := 0 }",
},
{
"package p; func _() { v, x := 0, 1 }",
"package p; func _() { x := 1 }",
},
{
"package p; func _() { v, x := f() }",
"package p; func _() { _, x := f() }",
},
{
"package p; func _() { v, x := fx(), fx() }",
"package p; func _() { _, x := fx(), fx() }",
},
{
"package p; func _() { v, _ := fx(), fx() }",
"package p; func _() { _, _ = fx(), fx() }",
},
{
"package p; func _() { _, v := fx(), fx() }",
"package p; func _() { _, _ = fx(), fx() }",
},
{
"package p; func _() { v := fx() }",
"package p; func _() { _ = fx() }",
},
// TODO(adonovan,pjw): change DeleteStmt's trailing comment handling.
// {
// "package p; func _() { v := 1 // comment\n }",
// "package p; func _() {}",
// },
{
"package p; func _() { v, x := 0, 1 // comment\n }",
"package p; func _() { x := 1 // comment\n }",
},
{
"package p; func _() { if v := 1; cond {} }", // (DeleteStmt fails within IfStmt)
"package p; func _() { if _ = 1; cond {} }",
},
{
"package p; func _() { if v, x := 1, 2; cond {} }",
"package p; func _() { if x := 2; cond {} }",
},
{
"package p; func _() { switch v := 0; cond {} }",
"package p; func _() { switch cond {} }",
},
{
"package p; func _() { switch v := fx(); cond {} }",
"package p; func _() { switch _ = fx(); cond {} }",
},
{
"package p; func _() { for v := 0; ; {} }",
"package p; func _() { for {} }",
},
// unhandled cases
{
"package p; func _(v int) {}", // parameter
"package p; func _(v int) {}",
},
{
"package p; func _() (v int) {}", // result
"package p; func _() (v int) {}",
},
{
"package p; type T int; func _(v T) {}", // receiver
"package p; type T int; func _(v T) {}",
},
// There is no defining Ident in this case.
// {
// "package p; func _() { switch v := any(nil).(type) {} }",
// "package p; func _() { switch v := any(nil).(type) {} }",
// },
} {
t.Run(fmt.Sprint(i), func(t *testing.T) {
t.Logf("src: %s", test.src)
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "p", test.src, parser.ParseComments) // allow errors
conf := types.Config{
Error: func(err error) {}, // allow errors
}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
}
files := []*ast.File{f}
conf.Check("p", fset, files, info) // ignore error
curId := func() inspector.Cursor {
for curId := range inspector.New(files).Root().Preorder((*ast.Ident)(nil)) {
id := curId.Node().(*ast.Ident)
if id.Name == "v" && info.Defs[id] != nil {
return curId
}
}
t.Fatalf("can't find Defs[v]")
panic("unreachable")
}()
tokFile := fset.File(f.Pos())
edits := analysisinternal.DeleteVar(tokFile, info, curId)
// TODO(adonovan): extract this helper for
// applying TextEdits and comparing against
// expectations. (This code was mostly copied
// from analysistest.)
var dedits []diff.Edit
for _, edit := range edits {
file := fset.File(edit.Pos)
dedits = append(dedits, diff.Edit{
Start: file.Offset(edit.Pos),
End: file.Offset(edit.End),
New: string(edit.NewText),
})
}
fixed, err := diff.ApplyBytes([]byte(test.src), dedits)
if err != nil {
t.Fatalf("diff.Apply: %v", err)
}
t.Logf("fixed: %s", fixed)
fixed, err = format.Source(fixed)
if err != nil {
t.Fatalf("format: %v", err)
}
want, err := format.Source([]byte(test.want))
if err != nil {
t.Fatalf("formatting want: %v", err)
}
t.Logf("want: %s", want)
unified := func(xlabel, ylabel string, x, y []byte) string {
x = append(slices.Clip(bytes.TrimSpace(x)), '\n')
y = append(slices.Clip(bytes.TrimSpace(y)), '\n')
return diff.Unified(xlabel, ylabel, string(x), string(y))
}
if diff := unified("fixed", "want", fixed, want); diff != "" {
t.Errorf("-- diff original fixed --\n%s\n"+
"-- diff fixed want --\n%s",
unified("original", "fixed", []byte(test.src), fixed),
diff)
}
})
}
}