2019-08-14 15:25:47 -06:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/format"
|
|
|
|
"go/parser"
|
|
|
|
"go/token"
|
|
|
|
"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
|
|
|
|
want []importInfo
|
|
|
|
unchanged bool // Expect added/deleted return value to be false.
|
|
|
|
}
|
|
|
|
|
|
|
|
type importInfo struct {
|
|
|
|
name string
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
var addTests = []test{
|
|
|
|
{
|
|
|
|
name: "leave os alone",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
)
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
unchanged: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "package statement only",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "package statement no new line",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "package statement comments",
|
|
|
|
pkg: "os",
|
|
|
|
in: `// This is a comment
|
|
|
|
package main // This too`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "package statement multiline comments",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main /* This is a multiline comment
|
|
|
|
and it extends
|
|
|
|
further down*/`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "import c",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
|
|
|
|
import "C"
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "C",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "existing imports",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
|
|
|
|
import "io"
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "io",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "existing imports with comment",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
|
|
|
|
import "io" // A comment
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "io",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "existing imports multiline comment",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
|
|
|
|
import "io" /* A comment
|
|
|
|
that
|
|
|
|
extends */
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
importInfo{
|
|
|
|
name: "",
|
|
|
|
path: "io",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "renamed import",
|
|
|
|
renamedPkg: "o",
|
|
|
|
pkg: "os",
|
|
|
|
in: `package main
|
|
|
|
`,
|
|
|
|
want: []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "o",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2019-08-16 13:02:11 -06:00
|
|
|
edits, err := addNamedImport(fset, file, test.renamedPkg, test.pkg)
|
2019-08-14 15:25:47 -06:00
|
|
|
if err != nil && !test.unchanged {
|
|
|
|
t.Errorf("error adding import: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the edits and parse the file.
|
|
|
|
got := applyEdits(test.in, edits)
|
|
|
|
gotFile := parse(t, test.name, got)
|
|
|
|
|
|
|
|
compareImports(t, fmt.Sprintf("first run: %s:\n", test.name), gotFile.Imports, test.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.
|
2019-08-16 13:02:11 -06:00
|
|
|
edits, err = addNamedImport(fset, gotFile, test.renamedPkg, test.pkg)
|
2019-08-14 15:25:47 -06:00
|
|
|
if err != nil && !test.unchanged {
|
|
|
|
t.Errorf("error adding import: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Apply the edits and parse the file.
|
|
|
|
got = applyEdits(got, edits)
|
|
|
|
gotFile = parse(t, test.name, got)
|
|
|
|
|
|
|
|
compareImports(t, test.name, gotFile.Imports, test.want)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDoubleAddNamedImport(t *testing.T) {
|
|
|
|
name := "doublenamedimport"
|
|
|
|
in := "package main\n"
|
|
|
|
file := parse(t, name, in)
|
|
|
|
// Add a named import
|
2019-08-16 13:02:11 -06:00
|
|
|
edits, err := addNamedImport(fset, file, "o", "os")
|
2019-08-14 15:25:47 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error adding import: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
got := applyEdits(in, edits)
|
|
|
|
gotFile := parse(t, name, got)
|
|
|
|
|
|
|
|
// Add a second named import
|
2019-08-16 13:02:11 -06:00
|
|
|
edits, err = addNamedImport(fset, gotFile, "i", "io")
|
2019-08-14 15:25:47 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error adding import: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
got = applyEdits(got, edits)
|
|
|
|
gotFile = parse(t, name, got)
|
|
|
|
|
|
|
|
want := []importInfo{
|
|
|
|
importInfo{
|
|
|
|
name: "o",
|
|
|
|
path: "os",
|
|
|
|
},
|
|
|
|
importInfo{
|
|
|
|
name: "i",
|
|
|
|
path: "io",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
compareImports(t, "", gotFile.Imports, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
func compareImports(t *testing.T, prefix string, got []*ast.ImportSpec, want []importInfo) {
|
|
|
|
if len(got) != len(want) {
|
|
|
|
t.Errorf("%s\ngot %d imports\nwant %d", prefix, len(got), len(want))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, imp := range got {
|
|
|
|
name := importName(imp)
|
|
|
|
path := importPath(imp)
|
|
|
|
found := false
|
|
|
|
for _, want := range want {
|
|
|
|
if want.name == name && want.path == path {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
t.Errorf("%s\n\ngot unexpected import: name: %q,path: %q", prefix, name, path)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func applyEdits(contents string, edits []TextEdit) string {
|
|
|
|
res := contents
|
|
|
|
|
|
|
|
// Apply the edits from the end of the file forward
|
|
|
|
// to preserve the offsets
|
|
|
|
for i := len(edits) - 1; i >= 0; i-- {
|
|
|
|
edit := edits[i]
|
|
|
|
start := edit.Span.Start().Offset()
|
|
|
|
end := edit.Span.End().Offset()
|
|
|
|
tmp := res[0:start] + edit.NewText
|
|
|
|
res = tmp + res[end:]
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|