2088 lines
25 KiB
Go
2088 lines
25 KiB
Go
// Copyright 2013 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 astutil
|
|
|
|
import (
|
|
"bytes"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/token"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
var fset = token.NewFileSet()
|
|
|
|
func parse(t *testing.T, name, in string) *ast.File {
|
|
file, err := parser.ParseFile(fset, name, in, parser.ParseComments)
|
|
if err != nil {
|
|
t.Fatalf("%s parse: %v", name, err)
|
|
}
|
|
return file
|
|
}
|
|
|
|
func print(t *testing.T, name string, f *ast.File) string {
|
|
var buf bytes.Buffer
|
|
if err := format.Node(&buf, fset, f); err != nil {
|
|
t.Fatalf("%s gofmt: %v", name, err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
type test struct {
|
|
name string
|
|
renamedPkg string
|
|
pkg string
|
|
in string
|
|
out string
|
|
unchanged bool // Expect added/deleted return value to be false.
|
|
}
|
|
|
|
var addTests = []test{
|
|
{
|
|
name: "leave os alone",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
import (
|
|
"os"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"os"
|
|
)
|
|
`,
|
|
unchanged: true,
|
|
},
|
|
{
|
|
name: "import.1",
|
|
pkg: "os",
|
|
in: `package main
|
|
`,
|
|
out: `package main
|
|
|
|
import "os"
|
|
`,
|
|
},
|
|
{
|
|
name: "import.2",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import "os"
|
|
`,
|
|
},
|
|
{
|
|
name: "import.3",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"io"
|
|
"utf8"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"utf8"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.17",
|
|
pkg: "x/y/z",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"a"
|
|
"b"
|
|
|
|
"x/w"
|
|
|
|
"d/f"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"a"
|
|
"b"
|
|
|
|
"x/w"
|
|
"x/y/z"
|
|
|
|
"d/f"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue #19190",
|
|
pkg: "x.org/y/z",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
|
|
"d.com/f"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
|
|
"d.com/f"
|
|
"x.org/y/z"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue #19190 with existing grouped import packages",
|
|
pkg: "x.org/y/z",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
|
|
"c.com/f"
|
|
"d.com/f"
|
|
|
|
"y.com/a"
|
|
"y.com/b"
|
|
"y.com/c"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
|
|
"c.com/f"
|
|
"d.com/f"
|
|
"x.org/y/z"
|
|
|
|
"y.com/a"
|
|
"y.com/b"
|
|
"y.com/c"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue #19190 - match score is still respected",
|
|
pkg: "y.org/c",
|
|
in: `package main
|
|
|
|
import (
|
|
"x.org/a"
|
|
|
|
"y.org/b"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"x.org/a"
|
|
|
|
"y.org/b"
|
|
"y.org/c"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import into singular group",
|
|
pkg: "bytes",
|
|
in: `package main
|
|
|
|
import "os"
|
|
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import into singular group with comment",
|
|
pkg: "bytes",
|
|
in: `package main
|
|
|
|
import /* why */ /* comment here? */ "os"
|
|
|
|
`,
|
|
out: `package main
|
|
|
|
import /* why */ /* comment here? */ (
|
|
"bytes"
|
|
"os"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import into group with leading comment",
|
|
pkg: "strings",
|
|
in: `package main
|
|
|
|
import (
|
|
// comment before bytes
|
|
"bytes"
|
|
"os"
|
|
)
|
|
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
// comment before bytes
|
|
"bytes"
|
|
"os"
|
|
"strings"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "",
|
|
renamedPkg: "fmtpkg",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import "os"
|
|
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
fmtpkg "fmt"
|
|
"os"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "struct comment",
|
|
pkg: "time",
|
|
in: `package main
|
|
|
|
// This is a comment before a struct.
|
|
type T struct {
|
|
t time.Time
|
|
}
|
|
`,
|
|
out: `package main
|
|
|
|
import "time"
|
|
|
|
// This is a comment before a struct.
|
|
type T struct {
|
|
t time.Time
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 8729 import C",
|
|
pkg: "time",
|
|
in: `package main
|
|
|
|
import "C"
|
|
|
|
// comment
|
|
type T time.Time
|
|
`,
|
|
out: `package main
|
|
|
|
import "C"
|
|
import "time"
|
|
|
|
// comment
|
|
type T time.Time
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 8729 empty import",
|
|
pkg: "time",
|
|
in: `package main
|
|
|
|
import ()
|
|
|
|
// comment
|
|
type T time.Time
|
|
`,
|
|
out: `package main
|
|
|
|
import "time"
|
|
|
|
// comment
|
|
type T time.Time
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 8729 comment on package line",
|
|
pkg: "time",
|
|
in: `package main // comment
|
|
|
|
type T time.Time
|
|
`,
|
|
out: `package main // comment
|
|
|
|
import "time"
|
|
|
|
type T time.Time
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 8729 comment after package",
|
|
pkg: "time",
|
|
in: `package main
|
|
// comment
|
|
|
|
type T time.Time
|
|
`,
|
|
out: `package main
|
|
|
|
import "time"
|
|
|
|
// comment
|
|
|
|
type T time.Time
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 8729 comment before and on package line",
|
|
pkg: "time",
|
|
in: `// comment before
|
|
package main // comment on
|
|
|
|
type T time.Time
|
|
`,
|
|
out: `// comment before
|
|
package main // comment on
|
|
|
|
import "time"
|
|
|
|
type T time.Time
|
|
`,
|
|
},
|
|
|
|
// Issue 9961: Match prefixes using path segments rather than bytes
|
|
{
|
|
name: "issue 9961",
|
|
pkg: "regexp",
|
|
in: `package main
|
|
|
|
import (
|
|
"flag"
|
|
"testing"
|
|
|
|
"rsc.io/p"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"flag"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"rsc.io/p"
|
|
)
|
|
`,
|
|
},
|
|
// Issue 10337: Preserve comment position
|
|
{
|
|
name: "issue 10337",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"bytes" // a
|
|
"log" // c
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bytes" // a
|
|
"fmt"
|
|
"log" // c
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 10337 new import at the start",
|
|
pkg: "bytes",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt" // b
|
|
"log" // c
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt" // b
|
|
"log" // c
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 10337 new import at the end",
|
|
pkg: "log",
|
|
in: `package main
|
|
|
|
import (
|
|
"bytes" // a
|
|
"fmt" // b
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bytes" // a
|
|
"fmt" // b
|
|
"log"
|
|
)
|
|
`,
|
|
},
|
|
// Issue 14075: Merge import declarations
|
|
{
|
|
name: "issue 14075",
|
|
pkg: "bufio",
|
|
in: `package main
|
|
|
|
import "bytes"
|
|
import "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 14075 update position",
|
|
pkg: "bufio",
|
|
in: `package main
|
|
|
|
import "bytes"
|
|
import (
|
|
"fmt"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 14075 ignore import "C"`,
|
|
pkg: "bufio",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import "bytes"
|
|
import "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 14075 ignore adjacent import "C"`,
|
|
pkg: "bufio",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 14075 ignore adjacent import "C" (without factored import)`,
|
|
pkg: "bufio",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 14075 ignore single import "C"`,
|
|
pkg: "bufio",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import "bufio"
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 17212 several single-import lines with shared prefix ending in a slash`,
|
|
pkg: "net/http",
|
|
in: `package main
|
|
|
|
import "bufio"
|
|
import "net/url"
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 17212 block imports lines with shared prefix ending in a slash`,
|
|
pkg: "net/http",
|
|
in: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"net/url"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: `issue 17213 many single-import lines`,
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import "bufio"
|
|
import "bytes"
|
|
import "errors"
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Issue 28605: Add specified import, even if that import path is imported under another name
|
|
{
|
|
name: "issue 28605 add unnamed path",
|
|
renamedPkg: "",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 28605 add pathpkg-renamed path",
|
|
renamedPkg: "pathpkg",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 28605 add blank identifier path",
|
|
renamedPkg: "_",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "issue 28605 add dot import path",
|
|
renamedPkg: ".",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
|
|
{
|
|
name: "duplicate import declarations, add existing one",
|
|
renamedPkg: "f",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import "fmt"
|
|
import "fmt"
|
|
import f "fmt"
|
|
import f "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
import "fmt"
|
|
import "fmt"
|
|
import f "fmt"
|
|
import f "fmt"
|
|
`,
|
|
unchanged: true,
|
|
},
|
|
}
|
|
|
|
func TestAddImport(t *testing.T) {
|
|
for _, test := range addTests {
|
|
file := parse(t, test.name, test.in)
|
|
var before bytes.Buffer
|
|
ast.Fprint(&before, fset, file, nil)
|
|
added := AddNamedImport(fset, file, test.renamedPkg, test.pkg)
|
|
if got := print(t, test.name, file); got != test.out {
|
|
t.Errorf("first run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
|
|
var after bytes.Buffer
|
|
ast.Fprint(&after, fset, file, nil)
|
|
t.Logf("AST before:\n%s\nAST after:\n%s\n", before.String(), after.String())
|
|
}
|
|
if got, want := added, !test.unchanged; got != want {
|
|
t.Errorf("first run: %s: added = %v, want %v", test.name, got, want)
|
|
}
|
|
|
|
// AddNamedImport should be idempotent. Verify that by calling it again,
|
|
// expecting no change to the AST, and the returned added value to always be false.
|
|
added = AddNamedImport(fset, file, test.renamedPkg, test.pkg)
|
|
if got := print(t, test.name, file); got != test.out {
|
|
t.Errorf("second run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
|
|
}
|
|
if got, want := added, false; got != want {
|
|
t.Errorf("second run: %s: added = %v, want %v", test.name, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDoubleAddImport(t *testing.T) {
|
|
file := parse(t, "doubleimport", "package main\n")
|
|
AddImport(fset, file, "os")
|
|
AddImport(fset, file, "bytes")
|
|
want := `package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
)
|
|
`
|
|
if got := print(t, "doubleimport", file); got != want {
|
|
t.Errorf("got: %s\nwant: %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDoubleAddNamedImport(t *testing.T) {
|
|
file := parse(t, "doublenamedimport", "package main\n")
|
|
AddNamedImport(fset, file, "o", "os")
|
|
AddNamedImport(fset, file, "i", "io")
|
|
want := `package main
|
|
|
|
import (
|
|
i "io"
|
|
o "os"
|
|
)
|
|
`
|
|
if got := print(t, "doublenamedimport", file); got != want {
|
|
t.Errorf("got: %s\nwant: %s", got, want)
|
|
}
|
|
}
|
|
|
|
// Part of issue 8729.
|
|
func TestDoubleAddImportWithDeclComment(t *testing.T) {
|
|
file := parse(t, "doubleimport", `package main
|
|
|
|
import (
|
|
)
|
|
|
|
// comment
|
|
type I int
|
|
`)
|
|
// The AddImport order here matters.
|
|
AddImport(fset, file, "golang.org/x/tools/go/ast/astutil")
|
|
AddImport(fset, file, "os")
|
|
want := `package main
|
|
|
|
import (
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
"os"
|
|
)
|
|
|
|
// comment
|
|
type I int
|
|
`
|
|
if got := print(t, "doubleimport_with_decl_comment", file); got != want {
|
|
t.Errorf("got: %s\nwant: %s", got, want)
|
|
}
|
|
}
|
|
|
|
var deleteTests = []test{
|
|
{
|
|
name: "import.4",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
import (
|
|
"os"
|
|
)
|
|
`,
|
|
out: `package main
|
|
`,
|
|
},
|
|
{
|
|
name: "import.5",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
import "os"
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
`,
|
|
},
|
|
{
|
|
name: "import.6",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"utf8"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
// Comment
|
|
import "C"
|
|
|
|
import (
|
|
"io"
|
|
"utf8"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.7",
|
|
pkg: "io",
|
|
in: `package main
|
|
|
|
import (
|
|
"io" // a
|
|
"os" // b
|
|
"utf8" // c
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
// a
|
|
"os" // b
|
|
"utf8" // c
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.8",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
import (
|
|
"io" // a
|
|
"os" // b
|
|
"utf8" // c
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io" // a
|
|
// b
|
|
"utf8" // c
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.9",
|
|
pkg: "utf8",
|
|
in: `package main
|
|
|
|
import (
|
|
"io" // a
|
|
"os" // b
|
|
"utf8" // c
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io" // a
|
|
"os" // b
|
|
// c
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.10",
|
|
pkg: "io",
|
|
in: `package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"utf8"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"os"
|
|
"utf8"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.11",
|
|
pkg: "os",
|
|
in: `package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"utf8"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io"
|
|
"utf8"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.12",
|
|
pkg: "utf8",
|
|
in: `package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"utf8"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "handle.raw.quote.imports",
|
|
pkg: "os",
|
|
in: "package main\n\nimport `os`",
|
|
out: `package main
|
|
`,
|
|
},
|
|
{
|
|
name: "import.13",
|
|
pkg: "io",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"io"
|
|
"os"
|
|
"utf8"
|
|
|
|
"go/format"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"os"
|
|
"utf8"
|
|
|
|
"go/format"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.14",
|
|
pkg: "io",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt" // a
|
|
|
|
"io" // b
|
|
"os" // c
|
|
"utf8" // d
|
|
|
|
"go/format" // e
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt" // a
|
|
|
|
// b
|
|
"os" // c
|
|
"utf8" // d
|
|
|
|
"go/format" // e
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.15",
|
|
pkg: "double",
|
|
in: `package main
|
|
|
|
import (
|
|
"double"
|
|
"double"
|
|
)
|
|
`,
|
|
out: `package main
|
|
`,
|
|
},
|
|
{
|
|
name: "import.16",
|
|
pkg: "bubble",
|
|
in: `package main
|
|
|
|
import (
|
|
"toil"
|
|
"bubble"
|
|
"bubble"
|
|
"trouble"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"toil"
|
|
"trouble"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.17",
|
|
pkg: "quad",
|
|
in: `package main
|
|
|
|
import (
|
|
"quad"
|
|
"quad"
|
|
)
|
|
|
|
import (
|
|
"quad"
|
|
"quad"
|
|
)
|
|
`,
|
|
out: `package main
|
|
`,
|
|
},
|
|
{
|
|
name: "import.18",
|
|
renamedPkg: "x",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
x "fmt"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.18",
|
|
renamedPkg: "x",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import x "fmt"
|
|
import y "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
import y "fmt"
|
|
`,
|
|
},
|
|
// Issue #15432, #18051
|
|
{
|
|
name: "import.19",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
// Some comment.
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
// Some comment.
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.20",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
// Some
|
|
// comment.
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
// Some
|
|
// comment.
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.21",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
/*
|
|
Some
|
|
comment.
|
|
*/
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
/*
|
|
Some
|
|
comment.
|
|
*/
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.22",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
/* Some */
|
|
// comment.
|
|
"io"
|
|
"fmt"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
/* Some */
|
|
// comment.
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.23",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
// comment 1
|
|
"fmt"
|
|
// comment 2
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
// comment 2
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.24",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt" // comment 1
|
|
"io" // comment 2
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io" // comment 2
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.25",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
/* comment */ "io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
/* comment */ "io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.26",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io" /* comment */
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io" /* comment */
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.27",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt" /* comment */
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.28",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import (
|
|
/* comment */ "fmt"
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
import (
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.29",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
"fmt"
|
|
"io" // comment 2
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
"io" // comment 2
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.30",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
"fmt" // comment 2
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.31",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
"fmt"
|
|
/* comment 2 */ "io"
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
/* comment 2 */ "io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.32",
|
|
pkg: "fmt",
|
|
renamedPkg: "f",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
f "fmt"
|
|
/* comment 2 */ i "io"
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
/* comment 2 */ i "io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.33",
|
|
pkg: "fmt",
|
|
renamedPkg: "f",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
/* comment 2 */ f "fmt"
|
|
i "io"
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
i "io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.34",
|
|
pkg: "fmt",
|
|
renamedPkg: "f",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
f "fmt" /* comment 2 */
|
|
i "io"
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
i "io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.35",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
"fmt"
|
|
// comment 2
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
// comment 1
|
|
import (
|
|
// comment 2
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.36",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
/* comment 1 */
|
|
import (
|
|
"fmt"
|
|
/* comment 2 */
|
|
"io"
|
|
)`,
|
|
out: `package main
|
|
|
|
/* comment 1 */
|
|
import (
|
|
/* comment 2 */
|
|
"io"
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Issue 20229: MergeLine panic on weird input
|
|
{
|
|
name: "import.37",
|
|
pkg: "io",
|
|
in: `package main
|
|
import("_"
|
|
"io")`,
|
|
out: `package main
|
|
|
|
import (
|
|
"_"
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Issue 28605: Delete specified import, even if that import path is imported under another name
|
|
{
|
|
name: "import.38",
|
|
renamedPkg: "",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.39",
|
|
renamedPkg: "pathpkg",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.40",
|
|
renamedPkg: "_",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.41",
|
|
renamedPkg: ".",
|
|
pkg: "path",
|
|
in: `package main
|
|
|
|
import (
|
|
"path"
|
|
. "path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"path"
|
|
_ "path"
|
|
pathpkg "path"
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Duplicate import declarations, all matching ones are deleted.
|
|
{
|
|
name: "import.42",
|
|
renamedPkg: "f",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import "fmt"
|
|
import "fmt"
|
|
import f "fmt"
|
|
import f "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
import "fmt"
|
|
import "fmt"
|
|
`,
|
|
},
|
|
{
|
|
name: "import.43",
|
|
renamedPkg: "x",
|
|
pkg: "fmt",
|
|
in: `package main
|
|
|
|
import "fmt"
|
|
import "fmt"
|
|
import f "fmt"
|
|
import f "fmt"
|
|
`,
|
|
out: `package main
|
|
|
|
import "fmt"
|
|
import "fmt"
|
|
import f "fmt"
|
|
import f "fmt"
|
|
`,
|
|
unchanged: true,
|
|
},
|
|
}
|
|
|
|
func TestDeleteImport(t *testing.T) {
|
|
for _, test := range deleteTests {
|
|
file := parse(t, test.name, test.in)
|
|
var before bytes.Buffer
|
|
ast.Fprint(&before, fset, file, nil)
|
|
deleted := DeleteNamedImport(fset, file, test.renamedPkg, test.pkg)
|
|
if got := print(t, test.name, file); got != test.out {
|
|
t.Errorf("first run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
|
|
var after bytes.Buffer
|
|
ast.Fprint(&after, fset, file, nil)
|
|
t.Logf("AST before:\n%s\nAST after:\n%s\n", before.String(), after.String())
|
|
}
|
|
if got, want := deleted, !test.unchanged; got != want {
|
|
t.Errorf("first run: %s: deleted = %v, want %v", test.name, got, want)
|
|
}
|
|
|
|
// DeleteNamedImport should be idempotent. Verify that by calling it again,
|
|
// expecting no change to the AST, and the returned deleted value to always be false.
|
|
deleted = DeleteNamedImport(fset, file, test.renamedPkg, test.pkg)
|
|
if got := print(t, test.name, file); got != test.out {
|
|
t.Errorf("second run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
|
|
}
|
|
if got, want := deleted, false; got != want {
|
|
t.Errorf("second run: %s: deleted = %v, want %v", test.name, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type rewriteTest struct {
|
|
name string
|
|
srcPkg string
|
|
dstPkg string
|
|
in string
|
|
out string
|
|
}
|
|
|
|
var rewriteTests = []rewriteTest{
|
|
{
|
|
name: "import.13",
|
|
srcPkg: "utf8",
|
|
dstPkg: "encoding/utf8",
|
|
in: `package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"utf8" // thanks ken
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"encoding/utf8" // thanks ken
|
|
"io"
|
|
"os"
|
|
)
|
|
`,
|
|
},
|
|
{
|
|
name: "import.14",
|
|
srcPkg: "asn1",
|
|
dstPkg: "encoding/asn1",
|
|
in: `package main
|
|
|
|
import (
|
|
"asn1"
|
|
"crypto"
|
|
"crypto/rsa"
|
|
_ "crypto/sha1"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"time"
|
|
)
|
|
|
|
var x = 1
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rsa"
|
|
_ "crypto/sha1"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"time"
|
|
)
|
|
|
|
var x = 1
|
|
`,
|
|
},
|
|
{
|
|
name: "import.15",
|
|
srcPkg: "url",
|
|
dstPkg: "net/url",
|
|
in: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"net"
|
|
"path"
|
|
"url"
|
|
)
|
|
|
|
var x = 1 // comment on x, not on url
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"bufio"
|
|
"net"
|
|
"net/url"
|
|
"path"
|
|
)
|
|
|
|
var x = 1 // comment on x, not on url
|
|
`,
|
|
},
|
|
{
|
|
name: "import.16",
|
|
srcPkg: "http",
|
|
dstPkg: "net/http",
|
|
in: `package main
|
|
|
|
import (
|
|
"flag"
|
|
"http"
|
|
"log"
|
|
"text/template"
|
|
)
|
|
|
|
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"net/http"
|
|
"text/template"
|
|
)
|
|
|
|
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
|
|
`,
|
|
},
|
|
}
|
|
|
|
func TestRewriteImport(t *testing.T) {
|
|
for _, test := range rewriteTests {
|
|
file := parse(t, test.name, test.in)
|
|
RewriteImport(fset, file, test.srcPkg, test.dstPkg)
|
|
if got := print(t, test.name, file); got != test.out {
|
|
t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out)
|
|
}
|
|
}
|
|
}
|
|
|
|
var importsTests = []struct {
|
|
name string
|
|
in string
|
|
want [][]string
|
|
}{
|
|
{
|
|
name: "no packages",
|
|
in: `package foo
|
|
`,
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "one group",
|
|
in: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
`,
|
|
want: [][]string{{"fmt", "testing"}},
|
|
},
|
|
{
|
|
name: "four groups",
|
|
in: `package foo
|
|
|
|
import "C"
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"appengine"
|
|
|
|
"myproject/mylib1"
|
|
"myproject/mylib2"
|
|
)
|
|
`,
|
|
want: [][]string{
|
|
{"C"},
|
|
{"fmt", "testing"},
|
|
{"appengine"},
|
|
{"myproject/mylib1", "myproject/mylib2"},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple factored groups",
|
|
in: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"appengine"
|
|
)
|
|
import (
|
|
"reflect"
|
|
|
|
"bytes"
|
|
)
|
|
`,
|
|
want: [][]string{
|
|
{"fmt", "testing"},
|
|
{"appengine"},
|
|
{"reflect"},
|
|
{"bytes"},
|
|
},
|
|
},
|
|
}
|
|
|
|
func unquote(s string) string {
|
|
res, err := strconv.Unquote(s)
|
|
if err != nil {
|
|
return "could_not_unquote"
|
|
}
|
|
return res
|
|
}
|
|
|
|
func TestImports(t *testing.T) {
|
|
fset := token.NewFileSet()
|
|
for _, test := range importsTests {
|
|
f, err := parser.ParseFile(fset, "test.go", test.in, 0)
|
|
if err != nil {
|
|
t.Errorf("%s: %v", test.name, err)
|
|
continue
|
|
}
|
|
var got [][]string
|
|
for _, group := range Imports(fset, f) {
|
|
var b []string
|
|
for _, spec := range group {
|
|
b = append(b, unquote(spec.Path.Value))
|
|
}
|
|
got = append(got, b)
|
|
}
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("Imports(%s)=%v, want %v", test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
var usesImportTests = []struct {
|
|
name string
|
|
path string
|
|
in string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "no packages",
|
|
path: "io",
|
|
in: `package foo
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.1",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import "io"
|
|
|
|
var _ io.Writer
|
|
`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "import.2",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import "io"
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.3",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import "io"
|
|
|
|
var io = 42
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.4",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import i "io"
|
|
|
|
var _ i.Writer
|
|
`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "import.5",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import i "io"
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.6",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import i "io"
|
|
|
|
var i = 42
|
|
var io = 42
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.7",
|
|
path: "encoding/json",
|
|
in: `package foo
|
|
|
|
import "encoding/json"
|
|
|
|
var _ json.Encoder
|
|
`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "import.8",
|
|
path: "encoding/json",
|
|
in: `package foo
|
|
|
|
import "encoding/json"
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.9",
|
|
path: "encoding/json",
|
|
in: `package foo
|
|
|
|
import "encoding/json"
|
|
|
|
var json = 42
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.10",
|
|
path: "encoding/json",
|
|
in: `package foo
|
|
|
|
import j "encoding/json"
|
|
|
|
var _ j.Encoder
|
|
`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "import.11",
|
|
path: "encoding/json",
|
|
in: `package foo
|
|
|
|
import j "encoding/json"
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.12",
|
|
path: "encoding/json",
|
|
in: `package foo
|
|
|
|
import j "encoding/json"
|
|
|
|
var j = 42
|
|
var json = 42
|
|
`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "import.13",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import _ "io"
|
|
`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "import.14",
|
|
path: "io",
|
|
in: `package foo
|
|
|
|
import . "io"
|
|
`,
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
func TestUsesImport(t *testing.T) {
|
|
fset := token.NewFileSet()
|
|
for _, test := range usesImportTests {
|
|
f, err := parser.ParseFile(fset, "test.go", test.in, 0)
|
|
if err != nil {
|
|
t.Errorf("%s: %v", test.name, err)
|
|
continue
|
|
}
|
|
got := UsesImport(f, test.path)
|
|
if got != test.want {
|
|
t.Errorf("UsesImport(%s)=%v, want %v", test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|