Exemplo n.º 1
0
// subpackages returns the set of packages in the given srcDir whose
// import paths start with dir.
func subpackages(ctxt *build.Context, srcDir string, dir string) map[string]bool {
	subs := map[string]bool{dir: true}

	// Find all packages under srcDir whose import paths start with dir.
	buildutil.ForEachPackage(ctxt, func(pkg string, err error) {
		if err != nil {
			log.Fatalf("unexpected error in ForEachPackage: %v", err)
		}

		if !strings.HasPrefix(pkg, path.Join(dir, "")) {
			return
		}

		p, err := ctxt.Import(pkg, "", build.FindOnly)
		if err != nil {
			log.Fatalf("unexpected: package %s can not be located by build context: %s", pkg, err)
		}
		if p.SrcRoot == "" {
			log.Fatalf("unexpected: could not determine srcDir for package %s: %s", pkg, err)
		}
		if p.SrcRoot != srcDir {
			return
		}

		subs[pkg] = true
	})

	return subs
}
Exemplo n.º 2
0
// Builds scans the specified Go workspace and builds the forward and
// reverse import dependency graphs for all its packages.
// It also returns a mapping from import paths to errors for packages
// that could not be loaded.
func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
	type importEdge struct {
		from, to string
	}
	type pathError struct {
		path string
		err  error
	}

	ch := make(chan interface{})

	var wg sync.WaitGroup
	buildutil.ForEachPackage(ctxt, func(path string, err error) {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err != nil {
				ch <- pathError{path, err}
				return
			}
			bp, err := ctxt.Import(path, "", 0)
			if _, ok := err.(*build.NoGoError); ok {
				return // empty directory is not an error
			}
			if err != nil {
				ch <- pathError{path, err}
				return
			}
			for _, imp := range bp.Imports {
				ch <- importEdge{path, imp}
			}
			for _, imp := range bp.TestImports {
				ch <- importEdge{path, imp}
			}
			for _, imp := range bp.XTestImports {
				ch <- importEdge{path, imp}
			}
		}()
	})
	go func() {
		wg.Wait()
		close(ch)
	}()

	forward = make(Graph)
	reverse = make(Graph)

	for e := range ch {
		switch e := e.(type) {
		case pathError:
			if errors == nil {
				errors = make(map[string]error)
			}
			errors[e.path] = e.err

		case importEdge:
			if e.to == "C" {
				continue // "C" is fake
			}
			forward.addEdge(e.from, e.to)
			reverse.addEdge(e.to, e.from)
		}
	}

	return forward, reverse, errors
}
Exemplo n.º 3
0
// Build scans the specified Go workspace and builds the forward and
// reverse import dependency graphs for all its packages.
// It also returns a mapping from canonical import paths to errors for packages
// whose loading was not entirely successful.
// A package may appear in the graph and in the errors mapping.
// All package paths are canonical and may contain "/vendor/".
func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
	type importEdge struct {
		from, to string
	}
	type pathError struct {
		path string
		err  error
	}

	ch := make(chan interface{})

	go func() {
		sema := make(chan int, 20) // I/O concurrency limiting semaphore
		var wg sync.WaitGroup
		buildutil.ForEachPackage(ctxt, func(path string, err error) {
			if err != nil {
				ch <- pathError{path, err}
				return
			}

			wg.Add(1)
			go func() {
				defer wg.Done()

				sema <- 1
				bp, err := ctxt.Import(path, "", 0)
				<-sema

				if err != nil {
					if _, ok := err.(*build.NoGoError); ok {
						// empty directory is not an error
					} else {
						ch <- pathError{path, err}
					}
					// Even in error cases, Import usually returns a package.
				}

				// absolutize resolves an import path relative
				// to the current package bp.
				// The absolute form may contain "vendor".
				//
				// The vendoring feature slows down Build by 3×.
				// Here are timings from a 1400 package workspace:
				//    1100ms: current code (with vendor check)
				//     880ms: with a nonblocking cache around ctxt.IsDir
				//     840ms: nonblocking cache with duplicate suppression
				//     340ms: original code (no vendor check)
				// TODO(adonovan): optimize, somehow.
				memo := make(map[string]string)
				absolutize := func(path string) string {
					canon, ok := memo[path]
					if !ok {
						sema <- 1
						bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly)
						<-sema

						if bp2 != nil {
							canon = bp2.ImportPath
						} else {
							canon = path
						}
						memo[path] = canon
					}
					return canon
				}

				if bp != nil {
					for _, imp := range bp.Imports {
						ch <- importEdge{path, absolutize(imp)}
					}
					for _, imp := range bp.TestImports {
						ch <- importEdge{path, absolutize(imp)}
					}
					for _, imp := range bp.XTestImports {
						ch <- importEdge{path, absolutize(imp)}
					}
				}

			}()
		})
		wg.Wait()
		close(ch)
	}()

	forward = make(Graph)
	reverse = make(Graph)

	for e := range ch {
		switch e := e.(type) {
		case pathError:
			if errors == nil {
				errors = make(map[string]error)
			}
			errors[e.path] = e.err

		case importEdge:
			if e.to == "C" {
				continue // "C" is fake
			}
			forward.addEdge(e.from, e.to)
			reverse.addEdge(e.to, e.from)
		}
	}

	return forward, reverse, errors
}
Exemplo n.º 4
0
func TestMoves(t *testing.T) {
	tests := []struct {
		ctxt     *build.Context
		from, to string
		want     map[string]string
	}{
		// Simple example.
		{
			ctxt: fakeContext(map[string][]string{
				"foo": {`package foo; type T int`},
				"main": {`package main

import "foo"

var _ foo.T
`},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/main/0.go": `package main

import "bar"

var _ bar.T
`,
				"/go/src/bar/0.go": `package bar

type T int
`,
			},
		},

		// Example with subpackage.
		{
			ctxt: fakeContext(map[string][]string{
				"foo":     {`package foo; type T int`},
				"foo/sub": {`package sub; type T int`},
				"main": {`package main

import "foo"
import "foo/sub"

var _ foo.T
var _ sub.T
`},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/main/0.go": `package main

import "bar"
import "bar/sub"

var _ bar.T
var _ sub.T
`,
				"/go/src/bar/0.go": `package bar

type T int
`,
				"/go/src/bar/sub/0.go": `package sub; type T int`,
			},
		},

		// References into subpackages
		{
			ctxt: fakeContext(map[string][]string{
				"foo":   {`package foo; import "foo/a"; var _ a.T`},
				"foo/a": {`package a; type T int`},
				"foo/b": {`package b; import "foo/a"; var _ a.T`},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/bar/0.go": `package bar

import "bar/a"

var _ a.T
`,
				"/go/src/bar/a/0.go": `package a; type T int`,
				"/go/src/bar/b/0.go": `package b

import "bar/a"

var _ a.T
`,
			},
		},

		// External test packages
		{
			ctxt: buildutil.FakeContext(map[string]map[string]string{
				"foo": {
					"0.go":      `package foo; type T int`,
					"0_test.go": `package foo_test; import "foo"; var _ foo.T`,
				},
				"baz": {
					"0_test.go": `package baz_test; import "foo"; var _ foo.T`,
				},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/bar/0.go": `package bar

type T int
`,
				"/go/src/bar/0_test.go": `package bar_test

import "bar"

var _ bar.T
`,
				"/go/src/baz/0_test.go": `package baz_test

import "bar"

var _ bar.T
`,
			},
		},
	}

	for _, test := range tests {
		ctxt := test.ctxt

		got := make(map[string]string)
		// Populate got with starting file set. rewriteFile and moveDirectory
		// will mutate got to produce resulting file set.
		buildutil.ForEachPackage(ctxt, func(importPath string, err error) {
			if err != nil {
				return
			}
			path := filepath.Join("/go/src", importPath, "0.go")
			if !buildutil.FileExists(ctxt, path) {
				return
			}
			f, err := ctxt.OpenFile(path)
			if err != nil {
				t.Errorf("unexpected error opening file: %s", err)
				return
			}
			bytes, err := ioutil.ReadAll(f)
			f.Close()
			if err != nil {
				t.Errorf("unexpected error reading file: %s", err)
				return
			}
			got[path] = string(bytes)
		})
		rewriteFile = func(fset *token.FileSet, f *ast.File, orig string) error {
			var out bytes.Buffer
			if err := format.Node(&out, fset, f); err != nil {
				return err
			}
			got[orig] = out.String()
			return nil
		}
		moveDirectory = func(from, to string) error {
			for path, contents := range got {
				if strings.HasPrefix(path, from) {
					newPath := strings.Replace(path, from, to, 1)
					delete(got, path)
					got[newPath] = contents
				}
			}
			return nil
		}

		err := Move(ctxt, test.from, test.to, "")
		prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
		if err != nil {
			t.Errorf("%s: unexpected error: %s", prefix, err)
			continue
		}

		for file, wantContent := range test.want {
			k := filepath.FromSlash(file)
			gotContent, ok := got[k]
			delete(got, k)
			if !ok {
				// TODO(matloob): some testcases might have files that won't be
				// rewritten
				t.Errorf("%s: file %s not rewritten", prefix, file)
				continue
			}
			if gotContent != wantContent {
				t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+
					"want <<<%s>>>", prefix, file, gotContent, wantContent)
			}
		}
		// got should now be empty
		for file := range got {
			t.Errorf("%s: unexpected rewrite of file %s", prefix, file)
		}
	}
}
Exemplo n.º 5
0
// Builds scans the specified Go workspace and builds the forward and
// reverse import dependency graphs for all its packages.
// It also returns a mapping from import paths to errors for packages
// that could not be loaded.
func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
	// The (sole) graph builder goroutine receives a stream of import
	// edges from the package loading goroutine.
	forward = make(Graph)
	reverse = make(Graph)
	edgec := make(chan [2]string)
	go func() {
		for edge := range edgec {
			if edge[1] == "C" {
				continue // "C" is fake
			}
			forward.addEdge(edge[0], edge[1])
			reverse.addEdge(edge[1], edge[0])
		}
	}()

	// The (sole) error goroutine receives a stream of ReadDir and
	// Import errors.
	type pathError struct {
		path string
		err  error
	}
	errorc := make(chan pathError)
	go func() {
		for e := range errorc {
			if errors == nil {
				errors = make(map[string]error)
			}
			errors[e.path] = e.err
		}
	}()

	var wg sync.WaitGroup
	buildutil.ForEachPackage(ctxt, func(path string, err error) {
		if err != nil {
			errorc <- pathError{path, err}
			return
		}
		wg.Add(1)
		// The import goroutines load the metadata for each package.
		go func(path string) {
			defer wg.Done()
			bp, err := ctxt.Import(path, "", 0)
			if _, ok := err.(*build.NoGoError); ok {
				return // empty directory is not an error
			}
			if err != nil {
				errorc <- pathError{path, err}
				return
			}
			for _, imp := range bp.Imports {
				edgec <- [2]string{path, imp}
			}
			for _, imp := range bp.TestImports {
				edgec <- [2]string{path, imp}
			}
			for _, imp := range bp.XTestImports {
				edgec <- [2]string{path, imp}
			}
		}(path)
	})
	wg.Wait()

	close(edgec)
	close(errorc)

	return forward, reverse, errors
}
Exemplo n.º 6
0
func (w *PkgWalker) LookupObjects(conf *PkgConfig, cursor *FileCursor) {
	var cursorObj types.Object
	var cursorSelection *types.Selection
	var cursorObjIsDef bool
	//lookup defs

	var pkg *types.Package
	var pkgInfo *types.Info
	if cursor.xtest {
		pkgInfo = conf.XInfo
		pkg = conf.XPkg
	} else {
		pkgInfo = conf.Info
		pkg = conf.Pkg
	}

	_ = cursorObjIsDef
	if cursorObj == nil {
		for sel, obj := range pkgInfo.Selections {
			if cursor.pos >= sel.Sel.Pos() && cursor.pos <= sel.Sel.End() {
				cursorObj = obj.Obj()
				cursorSelection = obj
				break
			}
		}
	}
	if cursorObj == nil {
		for id, obj := range pkgInfo.Defs {
			if cursor.pos >= id.Pos() && cursor.pos <= id.End() {
				cursorObj = obj
				cursorObjIsDef = true
				break
			}
		}
	}
	_ = cursorSelection
	if cursorObj == nil {
		for id, obj := range pkgInfo.Uses {
			if cursor.pos >= id.Pos() && cursor.pos <= id.End() {
				cursorObj = obj
				break
			}
		}
	}
	if cursorObj == nil {
		return
	}

	kind, err := parserObjKind(cursorObj)
	if err != nil {
		log.Fatalln(err)
	}

	if kind == ObjField {
		if cursorObj.(*types.Var).Anonymous() {
			typ := orgType(cursorObj.Type())
			if named, ok := typ.(*types.Named); ok {
				cursorObj = named.Obj()
			}
		}
	}
	cursorPkg := cursorObj.Pkg()
	cursorPos := cursorObj.Pos()
	//var fieldTypeInfo *types.Info
	var fieldTypeObj types.Object
	//	if cursorPkg == pkg {
	//		fieldTypeInfo = pkgInfo
	//	}
	cursorIsInterfaceMethod := false
	var cursorInterfaceTypeName string

	if kind == ObjMethod && cursorSelection != nil && cursorSelection.Recv() != nil {
		sig := cursorObj.(*types.Func).Type().Underlying().(*types.Signature)
		if _, ok := sig.Recv().Type().Underlying().(*types.Interface); ok {
			if named, ok := cursorSelection.Recv().(*types.Named); ok {
				obj, typ := w.lookupNamedMethod(named, cursorObj.Name())
				if obj != nil {
					cursorObj = obj
				}
				if typ != nil {
					cursorPkg = typ.Obj().Pkg()
					cursorInterfaceTypeName = typ.Obj().Name()
				}
				cursorIsInterfaceMethod = true
			}
		}
	} else if kind == ObjField && cursorSelection != nil {
		if recv := cursorSelection.Recv(); recv != nil {
			typ := orgType(recv)
			if typ != nil {
				if name, ok := typ.(*types.Named); ok {
					fieldTypeObj = name.Obj()
					na := w.lookupNamedField(name, cursorObj.Name())
					if na != nil {
						fieldTypeObj = na.Obj()
					}
				}
			}
		}
	}
	if cursorPkg != nil && cursorPkg != pkg &&
		kind != ObjPkgName && w.isBinaryPkg(cursorPkg.Path()) {
		conf := &PkgConfig{
			IgnoreFuncBodies: true,
			AllowBinary:      true,
			WithTestFiles:    true,
			Info: &types.Info{
				Defs: make(map[*ast.Ident]types.Object),
			},
		}
		pkg, _ := w.Import("", cursorPkg.Path(), conf)
		if pkg != nil {
			if cursorIsInterfaceMethod {
				for _, obj := range conf.Info.Defs {
					if obj == nil {
						continue
					}
					if fn, ok := obj.(*types.Func); ok {
						if fn.Name() == cursorObj.Name() {
							if sig, ok := fn.Type().Underlying().(*types.Signature); ok {
								if named, ok := sig.Recv().Type().(*types.Named); ok {
									if named.Obj() != nil && named.Obj().Name() == cursorInterfaceTypeName {
										cursorPos = obj.Pos()
										break
									}
								}
							}
						}
					}
				}
			} else if kind == ObjField && fieldTypeObj != nil {
				for _, obj := range conf.Info.Defs {
					if obj == nil {
						continue
					}
					if _, ok := obj.(*types.TypeName); ok {
						if IsSameObject(fieldTypeObj, obj) {
							if t, ok := obj.Type().Underlying().(*types.Struct); ok {
								for i := 0; i < t.NumFields(); i++ {
									if t.Field(i).Id() == cursorObj.Id() {
										cursorPos = t.Field(i).Pos()
										break
									}
								}
							}
							break
						}
					}
				}
			} else {
				for k, v := range conf.Info.Defs {
					if k != nil && v != nil && IsSameObject(v, cursorObj) {
						cursorPos = k.Pos()
						break
					}
				}
			}
		}
		//		if kind == ObjField || cursorIsInterfaceMethod {
		//			fieldTypeInfo = conf.Info
		//		}
	}
	//	if kind == ObjField {
	//		fieldTypeObj = w.LookupStructFromField(fieldTypeInfo, cursorPkg, cursorObj, cursorPos)
	//	}
	if typesFindDef {
		fmt.Println(w.fset.Position(cursorPos))
	}
	if typesFindInfo {
		if kind == ObjField && fieldTypeObj != nil {
			typeName := fieldTypeObj.Name()
			if fieldTypeObj.Pkg() != nil && fieldTypeObj.Pkg() != pkg {
				typeName = fieldTypeObj.Pkg().Name() + "." + fieldTypeObj.Name()
			}
			fmt.Println(typeName, simpleObjInfo(cursorObj))
		} else if kind == ObjBuiltin {
			fmt.Println(builtinInfo(cursorObj.Name()))
		} else if kind == ObjPkgName {
			fmt.Println(cursorObj.String())
		} else if cursorIsInterfaceMethod {
			fmt.Println(strings.Replace(simpleObjInfo(cursorObj), "(interface)", cursorPkg.Name()+"."+cursorInterfaceTypeName, 1))
		} else {
			fmt.Println(simpleObjInfo(cursorObj))
		}
	}

	if typesFindDoc && typesFindDef {
		pos := w.fset.Position(cursorPos)
		file := w.parsedFileCache[pos.Filename]
		if file != nil {
			line := pos.Line
			var group *ast.CommentGroup
			for _, v := range file.Comments {
				lastLine := w.fset.Position(v.End()).Line
				if lastLine == line || lastLine == line-1 {
					group = v
				} else if lastLine > line {
					break
				}
			}
			if group != nil {
				fmt.Println(group.Text())
			}
		}
	}
	if !typesFindUse {
		return
	}

	var usages []int
	if kind == ObjPkgName {
		for id, obj := range pkgInfo.Uses {
			if obj != nil && obj.Id() == cursorObj.Id() { //!= nil && cursorObj.Pos() == obj.Pos() {
				usages = append(usages, int(id.Pos()))
			}
		}
	} else {
		//		for id, obj := range pkgInfo.Defs {
		//			if obj == cursorObj { //!= nil && cursorObj.Pos() == obj.Pos() {
		//				usages = append(usages, int(id.Pos()))
		//			}
		//		}
		for id, obj := range pkgInfo.Uses {
			if obj == cursorObj { //!= nil && cursorObj.Pos() == obj.Pos() {
				usages = append(usages, int(id.Pos()))
			}
		}
	}
	var pkg_path string
	var xpkg_path string
	if conf.Pkg != nil {
		pkg_path = conf.Pkg.Path()
	}
	if conf.XPkg != nil {
		xpkg_path = conf.XPkg.Path()
	}

	if cursorPkg != nil &&
		(cursorPkg.Path() == pkg_path || cursorPkg.Path() == xpkg_path) &&
		kind != ObjPkgName {
		usages = append(usages, int(cursorPos))
	}

	(sort.IntSlice(usages)).Sort()
	for _, pos := range usages {
		fmt.Println(w.fset.Position(token.Pos(pos)))
	}
	//check look for current pkg.object on pkg_test
	if typesFindUseAll || IsSamePkg(cursorPkg, conf.Pkg) {
		var addInfo *types.Info
		if conf.Cursor.xtest {
			addInfo = conf.Info
		} else {
			addInfo = conf.XInfo
		}
		if addInfo != nil && cursorPkg != nil {
			var usages []int
			//		for id, obj := range addInfo.Defs {
			//			if id != nil && obj != nil && obj.Id() == cursorObj.Id() {
			//				usages = append(usages, int(id.Pos()))
			//			}
			//		}
			for k, v := range addInfo.Uses {
				if k != nil && v != nil && IsSameObject(v, cursorObj) {
					usages = append(usages, int(k.Pos()))
				}
			}
			(sort.IntSlice(usages)).Sort()
			for _, pos := range usages {
				fmt.Println(w.fset.Position(token.Pos(pos)))
			}
		}
	}
	if !typesFindUseAll {
		return
	}

	if cursorPkg == nil {
		return
	}

	var find_def_pkg string
	var uses_paths []string
	if cursorPkg.Path() != pkg_path && cursorPkg.Path() != xpkg_path {
		find_def_pkg = cursorPkg.Path()
		uses_paths = append(uses_paths, cursorPkg.Path())
	}

	buildutil.ForEachPackage(&build.Default, func(importPath string, err error) {
		if err != nil {
			return
		}
		if importPath == conf.Pkg.Path() {
			return
		}
		bp, err := w.importPath(importPath, 0)
		if err != nil {
			return
		}
		find := false
		if bp.ImportPath == cursorPkg.Path() {
			find = true
		} else {
			for _, v := range bp.Imports {
				if v == cursorObj.Pkg().Path() {
					find = true
					break
				}
			}
		}
		if find {
			for _, v := range uses_paths {
				if v == bp.ImportPath {
					return
				}
			}
			uses_paths = append(uses_paths, bp.ImportPath)
		}
	})

	w.imported = make(map[string]*types.Package)
	for _, v := range uses_paths {
		conf := &PkgConfig{
			IgnoreFuncBodies: false,
			AllowBinary:      true,
			WithTestFiles:    true,
			Info: &types.Info{
				Uses: make(map[*ast.Ident]types.Object),
			},
			XInfo: &types.Info{
				Uses: make(map[*ast.Ident]types.Object),
			},
		}
		w.imported[v] = nil
		var usages []int
		vpkg, _ := w.Import("", v, conf)
		if vpkg != nil && vpkg != pkg {
			if conf.Info != nil {
				for k, v := range conf.Info.Uses {
					if k != nil && v != nil && IsSameObject(v, cursorObj) {
						usages = append(usages, int(k.Pos()))
					}
				}
			}
			if conf.XInfo != nil {
				for k, v := range conf.XInfo.Uses {
					if k != nil && v != nil && IsSameObject(v, cursorObj) {
						usages = append(usages, int(k.Pos()))
					}
				}
			}
		}
		if v == find_def_pkg {
			usages = append(usages, int(cursorPos))
		}
		(sort.IntSlice(usages)).Sort()
		for _, pos := range usages {
			fmt.Println(w.fset.Position(token.Pos(pos)))
		}
	}
}
Exemplo n.º 7
0
func TestMoves(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("broken on Windows; see golang.org/issue/16384")
	}
	tests := []struct {
		ctxt         *build.Context
		from, to     string
		want         map[string]string
		wantWarnings []string
	}{
		// Simple example.
		{
			ctxt: fakeContext(map[string][]string{
				"foo": {`package foo; type T int`},
				"main": {`package main

import "foo"

var _ foo.T
`},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/main/0.go": `package main

import "bar"

var _ bar.T
`,
				"/go/src/bar/0.go": `package bar

type T int
`,
			},
		},

		// Example with subpackage.
		{
			ctxt: fakeContext(map[string][]string{
				"foo":     {`package foo; type T int`},
				"foo/sub": {`package sub; type T int`},
				"main": {`package main

import "foo"
import "foo/sub"

var _ foo.T
var _ sub.T
`},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/main/0.go": `package main

import "bar"
import "bar/sub"

var _ bar.T
var _ sub.T
`,
				"/go/src/bar/0.go": `package bar

type T int
`,
				"/go/src/bar/sub/0.go": `package sub; type T int`,
			},
		},

		// References into subpackages
		{
			ctxt: fakeContext(map[string][]string{
				"foo":   {`package foo; import "foo/a"; var _ a.T`},
				"foo/a": {`package a; type T int`},
				"foo/b": {`package b; import "foo/a"; var _ a.T`},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/bar/0.go": `package bar

import "bar/a"

var _ a.T
`,
				"/go/src/bar/a/0.go": `package a; type T int`,
				"/go/src/bar/b/0.go": `package b

import "bar/a"

var _ a.T
`,
			},
		},

		// External test packages
		{
			ctxt: buildutil.FakeContext(map[string]map[string]string{
				"foo": {
					"0.go":      `package foo; type T int`,
					"0_test.go": `package foo_test; import "foo"; var _ foo.T`,
				},
				"baz": {
					"0_test.go": `package baz_test; import "foo"; var _ foo.T`,
				},
			}),
			from: "foo", to: "bar",
			want: map[string]string{
				"/go/src/bar/0.go": `package bar

type T int
`,
				"/go/src/bar/0_test.go": `package bar_test

import "bar"

var _ bar.T
`,
				"/go/src/baz/0_test.go": `package baz_test

import "bar"

var _ bar.T
`,
			},
		},
		// package import comments
		{
			ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
			from: "foo", to: "bar",
			want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
`},
		},
		{
			ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import "baz" */`}}),
			from: "foo", to: "bar",
			want: map[string]string{"/go/src/bar/0.go": `package bar /* import "bar" */
`},
		},
		{
			ctxt: fakeContext(map[string][]string{"foo": {`package foo       // import "baz"`}}),
			from: "foo", to: "bar",
			want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
`},
		},
		{
			ctxt: fakeContext(map[string][]string{"foo": {`package foo
// import " this is not an import comment`}}),
			from: "foo", to: "bar",
			want: map[string]string{"/go/src/bar/0.go": `package bar

// import " this is not an import comment
`},
		},
		{
			ctxt: fakeContext(map[string][]string{"foo": {`package foo
/* import " this is not an import comment */`}}),
			from: "foo", to: "bar",
			want: map[string]string{"/go/src/bar/0.go": `package bar

/* import " this is not an import comment */
`},
		},
		// Import name conflict generates a warning, not an error.
		{
			ctxt: fakeContext(map[string][]string{
				"x": {},
				"a": {`package a; type A int`},
				"b": {`package b; type B int`},
				"conflict": {`package conflict

import "a"
import "b"
var _ a.A
var _ b.B
`},
				"ok": {`package ok
import "b"
var _ b.B
`},
			}),
			from: "b", to: "x/a",
			want: map[string]string{
				"/go/src/a/0.go": `package a; type A int`,
				"/go/src/ok/0.go": `package ok

import "x/a"

var _ a.B
`,
				"/go/src/conflict/0.go": `package conflict

import "a"
import "x/a"

var _ a.A
var _ b.B
`,
				"/go/src/x/a/0.go": `package a

type B int
`,
			},
			wantWarnings: []string{
				`/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`,
				`/go/src/conflict/0.go:3:8: 	conflicts with imported package name in same block`,
				`/go/src/conflict/0.go:3:8: skipping update of this file`,
			},
		},
		// Rename with same base name.
		{
			ctxt: fakeContext(map[string][]string{
				"x": {},
				"y": {},
				"x/foo": {`package foo

type T int
`},
				"main": {`package main; import "x/foo"; var _ foo.T`},
			}),
			from: "x/foo", to: "y/foo",
			want: map[string]string{
				"/go/src/y/foo/0.go": `package foo

type T int
`,
				"/go/src/main/0.go": `package main

import "y/foo"

var _ foo.T
`,
			},
		},
	}

	for _, test := range tests {
		ctxt := test.ctxt

		got := make(map[string]string)
		// Populate got with starting file set. rewriteFile and moveDirectory
		// will mutate got to produce resulting file set.
		buildutil.ForEachPackage(ctxt, func(importPath string, err error) {
			if err != nil {
				return
			}
			path := filepath.Join("/go/src", importPath, "0.go")
			if !buildutil.FileExists(ctxt, path) {
				return
			}
			f, err := ctxt.OpenFile(path)
			if err != nil {
				t.Errorf("unexpected error opening file: %s", err)
				return
			}
			bytes, err := ioutil.ReadAll(f)
			f.Close()
			if err != nil {
				t.Errorf("unexpected error reading file: %s", err)
				return
			}
			got[path] = string(bytes)
		})
		var warnings []string
		reportError = func(posn token.Position, message string) {
			warning := fmt.Sprintf("%s:%d:%d: %s",
				filepath.ToSlash(posn.Filename), // for MS Windows
				posn.Line,
				posn.Column,
				message)
			warnings = append(warnings, warning)

		}
		writeFile = func(filename string, content []byte) error {
			got[filename] = string(content)
			return nil
		}
		moveDirectory = func(from, to string) error {
			for path, contents := range got {
				if strings.HasPrefix(path, from) {
					newPath := strings.Replace(path, from, to, 1)
					delete(got, path)
					got[newPath] = contents
				}
			}
			return nil
		}

		err := Move(ctxt, test.from, test.to, "")
		prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
		if err != nil {
			t.Errorf("%s: unexpected error: %s", prefix, err)
			continue
		}

		if !reflect.DeepEqual(warnings, test.wantWarnings) {
			t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s",
				prefix,
				strings.Join(warnings, "\n"),
				strings.Join(test.wantWarnings, "\n"))
		}

		for file, wantContent := range test.want {
			k := filepath.FromSlash(file)
			gotContent, ok := got[k]
			delete(got, k)
			if !ok {
				// TODO(matloob): some testcases might have files that won't be
				// rewritten
				t.Errorf("%s: file %s not rewritten", prefix, file)
				continue
			}
			if gotContent != wantContent {
				t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+
					"want <<<%s>>>", prefix, file, gotContent, wantContent)
			}
		}
		// got should now be empty
		for file := range got {
			t.Errorf("%s: unexpected rewrite of file %s", prefix, file)
		}
	}
}