Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
1
vendor/golang.org/x/tools/refactor/README
generated
vendored
Normal file
1
vendor/golang.org/x/tools/refactor/README
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
golang.org/x/tools/refactor: libraries for refactoring tools.
|
373
vendor/golang.org/x/tools/refactor/eg/eg.go
generated
vendored
Normal file
373
vendor/golang.org/x/tools/refactor/eg/eg.go
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
// Copyright 2014 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 eg implements the example-based refactoring tool whose
|
||||
// command-line is defined in golang.org/x/tools/cmd/eg.
|
||||
package eg // import "golang.org/x/tools/refactor/eg"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
const Help = `
|
||||
This tool implements example-based refactoring of expressions.
|
||||
|
||||
The transformation is specified as a Go file defining two functions,
|
||||
'before' and 'after', of identical types. Each function body consists
|
||||
of a single statement: either a return statement with a single
|
||||
(possibly multi-valued) expression, or an expression statement. The
|
||||
'before' expression specifies a pattern and the 'after' expression its
|
||||
replacement.
|
||||
|
||||
package P
|
||||
import ( "errors"; "fmt" )
|
||||
func before(s string) error { return fmt.Errorf("%s", s) }
|
||||
func after(s string) error { return errors.New(s) }
|
||||
|
||||
The expression statement form is useful when the expression has no
|
||||
result, for example:
|
||||
|
||||
func before(msg string) { log.Fatalf("%s", msg) }
|
||||
func after(msg string) { log.Fatal(msg) }
|
||||
|
||||
The parameters of both functions are wildcards that may match any
|
||||
expression assignable to that type. If the pattern contains multiple
|
||||
occurrences of the same parameter, each must match the same expression
|
||||
in the input for the pattern to match. If the replacement contains
|
||||
multiple occurrences of the same parameter, the expression will be
|
||||
duplicated, possibly changing the side-effects.
|
||||
|
||||
The tool analyses all Go code in the packages specified by the
|
||||
arguments, replacing all occurrences of the pattern with the
|
||||
substitution.
|
||||
|
||||
So, the transform above would change this input:
|
||||
err := fmt.Errorf("%s", "error: " + msg)
|
||||
to this output:
|
||||
err := errors.New("error: " + msg)
|
||||
|
||||
Identifiers, including qualified identifiers (p.X) are considered to
|
||||
match only if they denote the same object. This allows correct
|
||||
matching even in the presence of dot imports, named imports and
|
||||
locally shadowed package names in the input program.
|
||||
|
||||
Matching of type syntax is semantic, not syntactic: type syntax in the
|
||||
pattern matches type syntax in the input if the types are identical.
|
||||
Thus, func(x int) matches func(y int).
|
||||
|
||||
This tool was inspired by other example-based refactoring tools,
|
||||
'gofmt -r' for Go and Refaster for Java.
|
||||
|
||||
|
||||
LIMITATIONS
|
||||
===========
|
||||
|
||||
EXPRESSIVENESS
|
||||
|
||||
Only refactorings that replace one expression with another, regardless
|
||||
of the expression's context, may be expressed. Refactoring arbitrary
|
||||
statements (or sequences of statements) is a less well-defined problem
|
||||
and is less amenable to this approach.
|
||||
|
||||
A pattern that contains a function literal (and hence statements)
|
||||
never matches.
|
||||
|
||||
There is no way to generalize over related types, e.g. to express that
|
||||
a wildcard may have any integer type, for example.
|
||||
|
||||
It is not possible to replace an expression by one of a different
|
||||
type, even in contexts where this is legal, such as x in fmt.Print(x).
|
||||
|
||||
The struct literals T{x} and T{K: x} cannot both be matched by a single
|
||||
template.
|
||||
|
||||
|
||||
SAFETY
|
||||
|
||||
Verifying that a transformation does not introduce type errors is very
|
||||
complex in the general case. An innocuous-looking replacement of one
|
||||
constant by another (e.g. 1 to 2) may cause type errors relating to
|
||||
array types and indices, for example. The tool performs only very
|
||||
superficial checks of type preservation.
|
||||
|
||||
|
||||
IMPORTS
|
||||
|
||||
Although the matching algorithm is fully aware of scoping rules, the
|
||||
replacement algorithm is not, so the replacement code may contain
|
||||
incorrect identifier syntax for imported objects if there are dot
|
||||
imports, named imports or locally shadowed package names in the input
|
||||
program.
|
||||
|
||||
Imports are added as needed, but they are not removed as needed.
|
||||
Run 'goimports' on the modified file for now.
|
||||
|
||||
Dot imports are forbidden in the template.
|
||||
|
||||
|
||||
TIPS
|
||||
====
|
||||
|
||||
Sometimes a little creativity is required to implement the desired
|
||||
migration. This section lists a few tips and tricks.
|
||||
|
||||
To remove the final parameter from a function, temporarily change the
|
||||
function signature so that the final parameter is variadic, as this
|
||||
allows legal calls both with and without the argument. Then use eg to
|
||||
remove the final argument from all callers, and remove the variadic
|
||||
parameter by hand. The reverse process can be used to add a final
|
||||
parameter.
|
||||
|
||||
To add or remove parameters other than the final one, you must do it in
|
||||
stages: (1) declare a variant function f' with a different name and the
|
||||
desired parameters; (2) use eg to transform calls to f into calls to f',
|
||||
changing the arguments as needed; (3) change the declaration of f to
|
||||
match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
|
||||
`
|
||||
|
||||
// TODO(adonovan): expand upon the above documentation as an HTML page.
|
||||
|
||||
// A Transformer represents a single example-based transformation.
|
||||
type Transformer struct {
|
||||
fset *token.FileSet
|
||||
verbose bool
|
||||
info *types.Info // combined type info for template/input/output ASTs
|
||||
seenInfos map[*types.Info]bool
|
||||
wildcards map[*types.Var]bool // set of parameters in func before()
|
||||
env map[string]ast.Expr // maps parameter name to wildcard binding
|
||||
importedObjs map[types.Object]*ast.SelectorExpr // objects imported by after().
|
||||
before, after ast.Expr
|
||||
afterStmts []ast.Stmt
|
||||
allowWildcards bool
|
||||
|
||||
// Working state of Transform():
|
||||
nsubsts int // number of substitutions made
|
||||
currentPkg *types.Package // package of current call
|
||||
}
|
||||
|
||||
// NewTransformer returns a transformer based on the specified template,
|
||||
// a single-file package containing "before" and "after" functions as
|
||||
// described in the package documentation.
|
||||
// tmplInfo is the type information for tmplFile.
|
||||
//
|
||||
func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) {
|
||||
// Check the template.
|
||||
beforeSig := funcSig(tmplPkg, "before")
|
||||
if beforeSig == nil {
|
||||
return nil, fmt.Errorf("no 'before' func found in template")
|
||||
}
|
||||
afterSig := funcSig(tmplPkg, "after")
|
||||
if afterSig == nil {
|
||||
return nil, fmt.Errorf("no 'after' func found in template")
|
||||
}
|
||||
|
||||
// TODO(adonovan): should we also check the names of the params match?
|
||||
if !types.Identical(afterSig, beforeSig) {
|
||||
return nil, fmt.Errorf("before %s and after %s functions have different signatures",
|
||||
beforeSig, afterSig)
|
||||
}
|
||||
|
||||
for _, imp := range tmplFile.Imports {
|
||||
if imp.Name != nil && imp.Name.Name == "." {
|
||||
// Dot imports are currently forbidden. We
|
||||
// make the simplifying assumption that all
|
||||
// imports are regular, without local renames.
|
||||
return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value)
|
||||
}
|
||||
}
|
||||
var beforeDecl, afterDecl *ast.FuncDecl
|
||||
for _, decl := range tmplFile.Decls {
|
||||
if decl, ok := decl.(*ast.FuncDecl); ok {
|
||||
switch decl.Name.Name {
|
||||
case "before":
|
||||
beforeDecl = decl
|
||||
case "after":
|
||||
afterDecl = decl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
before, err := soleExpr(beforeDecl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("before: %s", err)
|
||||
}
|
||||
afterStmts, after, err := stmtAndExpr(afterDecl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("after: %s", err)
|
||||
}
|
||||
|
||||
wildcards := make(map[*types.Var]bool)
|
||||
for i := 0; i < beforeSig.Params().Len(); i++ {
|
||||
wildcards[beforeSig.Params().At(i)] = true
|
||||
}
|
||||
|
||||
// checkExprTypes returns an error if Tb (type of before()) is not
|
||||
// safe to replace with Ta (type of after()).
|
||||
//
|
||||
// Only superficial checks are performed, and they may result in both
|
||||
// false positives and negatives.
|
||||
//
|
||||
// Ideally, we would only require that the replacement be assignable
|
||||
// to the context of a specific pattern occurrence, but the type
|
||||
// checker doesn't record that information and it's complex to deduce.
|
||||
// A Go type cannot capture all the constraints of a given expression
|
||||
// context, which may include the size, constness, signedness,
|
||||
// namedness or constructor of its type, and even the specific value
|
||||
// of the replacement. (Consider the rule that array literal keys
|
||||
// must be unique.) So we cannot hope to prove the safety of a
|
||||
// transformation in general.
|
||||
Tb := tmplInfo.TypeOf(before)
|
||||
Ta := tmplInfo.TypeOf(after)
|
||||
if types.AssignableTo(Tb, Ta) {
|
||||
// safe: replacement is assignable to pattern.
|
||||
} else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
|
||||
// safe: pattern has void type (must appear in an ExprStmt).
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb)
|
||||
}
|
||||
|
||||
tr := &Transformer{
|
||||
fset: fset,
|
||||
verbose: verbose,
|
||||
wildcards: wildcards,
|
||||
allowWildcards: true,
|
||||
seenInfos: make(map[*types.Info]bool),
|
||||
importedObjs: make(map[types.Object]*ast.SelectorExpr),
|
||||
before: before,
|
||||
after: after,
|
||||
afterStmts: afterStmts,
|
||||
}
|
||||
|
||||
// Combine type info from the template and input packages, and
|
||||
// type info for the synthesized ASTs too. This saves us
|
||||
// having to book-keep where each ast.Node originated as we
|
||||
// construct the resulting hybrid AST.
|
||||
tr.info = &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
}
|
||||
mergeTypeInfo(tr.info, tmplInfo)
|
||||
|
||||
// Compute set of imported objects required by after().
|
||||
// TODO(adonovan): reject dot-imports in pattern
|
||||
ast.Inspect(after, func(n ast.Node) bool {
|
||||
if n, ok := n.(*ast.SelectorExpr); ok {
|
||||
if _, ok := tr.info.Selections[n]; !ok {
|
||||
// qualified ident
|
||||
obj := tr.info.Uses[n.Sel]
|
||||
tr.importedObjs[obj] = n
|
||||
return false // prune
|
||||
}
|
||||
}
|
||||
return true // recur
|
||||
})
|
||||
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// WriteAST is a convenience function that writes AST f to the specified file.
|
||||
func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) {
|
||||
fh, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err2 := fh.Close(); err != nil {
|
||||
err = err2 // prefer earlier error
|
||||
}
|
||||
}()
|
||||
return format.Node(fh, fset, f)
|
||||
}
|
||||
|
||||
// -- utilities --------------------------------------------------------
|
||||
|
||||
// funcSig returns the signature of the specified package-level function.
|
||||
func funcSig(pkg *types.Package, name string) *types.Signature {
|
||||
if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok {
|
||||
return f.Type().(*types.Signature)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// soleExpr returns the sole expression in the before/after template function.
|
||||
func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) {
|
||||
if fn.Body == nil {
|
||||
return nil, fmt.Errorf("no body")
|
||||
}
|
||||
if len(fn.Body.List) != 1 {
|
||||
return nil, fmt.Errorf("must contain a single statement")
|
||||
}
|
||||
switch stmt := fn.Body.List[0].(type) {
|
||||
case *ast.ReturnStmt:
|
||||
if len(stmt.Results) != 1 {
|
||||
return nil, fmt.Errorf("return statement must have a single operand")
|
||||
}
|
||||
return stmt.Results[0], nil
|
||||
|
||||
case *ast.ExprStmt:
|
||||
return stmt.X, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("must contain a single return or expression statement")
|
||||
}
|
||||
|
||||
// stmtAndExpr returns the expression in the last return statement as well as the preceeding lines.
|
||||
func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) {
|
||||
if fn.Body == nil {
|
||||
return nil, nil, fmt.Errorf("no body")
|
||||
}
|
||||
|
||||
n := len(fn.Body.List)
|
||||
if n == 0 {
|
||||
return nil, nil, fmt.Errorf("must contain at least one statement")
|
||||
}
|
||||
|
||||
stmts, last := fn.Body.List[:n-1], fn.Body.List[n-1]
|
||||
|
||||
switch last := last.(type) {
|
||||
case *ast.ReturnStmt:
|
||||
if len(last.Results) != 1 {
|
||||
return nil, nil, fmt.Errorf("return statement must have a single operand")
|
||||
}
|
||||
return stmts, last.Results[0], nil
|
||||
|
||||
case *ast.ExprStmt:
|
||||
return stmts, last.X, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("must end with a single return or expression statement")
|
||||
}
|
||||
|
||||
// mergeTypeInfo adds type info from src to dst.
|
||||
func mergeTypeInfo(dst, src *types.Info) {
|
||||
for k, v := range src.Types {
|
||||
dst.Types[k] = v
|
||||
}
|
||||
for k, v := range src.Defs {
|
||||
dst.Defs[k] = v
|
||||
}
|
||||
for k, v := range src.Uses {
|
||||
dst.Uses[k] = v
|
||||
}
|
||||
for k, v := range src.Selections {
|
||||
dst.Selections[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// (debugging only)
|
||||
func astString(fset *token.FileSet, n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, fset, n)
|
||||
return buf.String()
|
||||
}
|
166
vendor/golang.org/x/tools/refactor/eg/eg_test.go
generated
vendored
Normal file
166
vendor/golang.org/x/tools/refactor/eg/eg_test.go
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package eg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
exact "go/constant"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/refactor/eg"
|
||||
)
|
||||
|
||||
// TODO(adonovan): more tests:
|
||||
// - of command-line tool
|
||||
// - of all parts of syntax
|
||||
// - of applying a template to a package it imports:
|
||||
// the replacement syntax should use unqualified names for its objects.
|
||||
|
||||
var (
|
||||
updateFlag = flag.Bool("update", false, "update the golden files")
|
||||
verboseFlag = flag.Bool("verbose", false, "show matcher information")
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
|
||||
}
|
||||
|
||||
conf := loader.Config{
|
||||
Fset: token.NewFileSet(),
|
||||
ParserMode: parser.ParseComments,
|
||||
}
|
||||
|
||||
// Each entry is a single-file package.
|
||||
// (Multi-file packages aren't interesting for this test.)
|
||||
// Order matters: each non-template package is processed using
|
||||
// the preceding template package.
|
||||
for _, filename := range []string{
|
||||
"testdata/A.template",
|
||||
"testdata/A1.go",
|
||||
"testdata/A2.go",
|
||||
|
||||
"testdata/B.template",
|
||||
"testdata/B1.go",
|
||||
|
||||
"testdata/C.template",
|
||||
"testdata/C1.go",
|
||||
|
||||
"testdata/D.template",
|
||||
"testdata/D1.go",
|
||||
|
||||
"testdata/E.template",
|
||||
"testdata/E1.go",
|
||||
|
||||
"testdata/F.template",
|
||||
"testdata/F1.go",
|
||||
|
||||
"testdata/G.template",
|
||||
"testdata/G1.go",
|
||||
|
||||
"testdata/H.template",
|
||||
"testdata/H1.go",
|
||||
|
||||
"testdata/I.template",
|
||||
"testdata/I1.go",
|
||||
|
||||
"testdata/J.template",
|
||||
"testdata/J1.go",
|
||||
|
||||
"testdata/bad_type.template",
|
||||
"testdata/no_before.template",
|
||||
"testdata/no_after_return.template",
|
||||
"testdata/type_mismatch.template",
|
||||
"testdata/expr_type_mismatch.template",
|
||||
} {
|
||||
pkgname := strings.TrimSuffix(filepath.Base(filename), ".go")
|
||||
conf.CreateFromFilenames(pkgname, filename)
|
||||
}
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var xform *eg.Transformer
|
||||
for _, info := range iprog.Created {
|
||||
file := info.Files[0]
|
||||
filename := iprog.Fset.File(file.Pos()).Name() // foo.go
|
||||
|
||||
if strings.HasSuffix(filename, "template") {
|
||||
// a new template
|
||||
shouldFail, _ := info.Pkg.Scope().Lookup("shouldFail").(*types.Const)
|
||||
xform, err = eg.NewTransformer(iprog.Fset, info.Pkg, file, &info.Info, *verboseFlag)
|
||||
if err != nil {
|
||||
if shouldFail == nil {
|
||||
t.Errorf("NewTransformer(%s): %s", filename, err)
|
||||
} else if want := exact.StringVal(shouldFail.Val()); !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("NewTransformer(%s): got error %q, want error %q", filename, err, want)
|
||||
}
|
||||
} else if shouldFail != nil {
|
||||
t.Errorf("NewTransformer(%s) succeeded unexpectedly; want error %q",
|
||||
filename, shouldFail.Val())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if xform == nil {
|
||||
t.Errorf("%s: no previous template", filename)
|
||||
continue
|
||||
}
|
||||
|
||||
// apply previous template to this package
|
||||
n := xform.Transform(&info.Info, info.Pkg, file)
|
||||
if n == 0 {
|
||||
t.Errorf("%s: no matches", filename)
|
||||
continue
|
||||
}
|
||||
|
||||
got := filename + "t" // foo.got
|
||||
golden := filename + "lden" // foo.golden
|
||||
|
||||
// Write actual output to foo.got.
|
||||
if err := eg.WriteAST(iprog.Fset, got, file); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(got)
|
||||
|
||||
// Compare foo.got with foo.golden.
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
cmd = exec.Command("/bin/diff", "-c", golden, got)
|
||||
default:
|
||||
cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Errorf("eg tests for %s failed: %s.\n%s\n", filename, err, buf)
|
||||
|
||||
if *updateFlag {
|
||||
t.Logf("Updating %s...", golden)
|
||||
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
|
||||
t.Errorf("Update failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
249
vendor/golang.org/x/tools/refactor/eg/match.go
generated
vendored
Normal file
249
vendor/golang.org/x/tools/refactor/eg/match.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 2014 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 eg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// matchExpr reports whether pattern x matches y.
|
||||
//
|
||||
// If tr.allowWildcards, Idents in x that refer to parameters are
|
||||
// treated as wildcards, and match any y that is assignable to the
|
||||
// parameter type; matchExpr records this correspondence in tr.env.
|
||||
// Otherwise, matchExpr simply reports whether the two trees are
|
||||
// equivalent.
|
||||
//
|
||||
// A wildcard appearing more than once in the pattern must
|
||||
// consistently match the same tree.
|
||||
//
|
||||
func (tr *Transformer) matchExpr(x, y ast.Expr) bool {
|
||||
if x == nil && y == nil {
|
||||
return true
|
||||
}
|
||||
if x == nil || y == nil {
|
||||
return false
|
||||
}
|
||||
x = unparen(x)
|
||||
y = unparen(y)
|
||||
|
||||
// Is x a wildcard? (a reference to a 'before' parameter)
|
||||
if xobj, ok := tr.wildcardObj(x); ok {
|
||||
return tr.matchWildcard(xobj, y)
|
||||
}
|
||||
|
||||
// Object identifiers (including pkg-qualified ones)
|
||||
// are handled semantically, not syntactically.
|
||||
xobj := isRef(x, tr.info)
|
||||
yobj := isRef(y, tr.info)
|
||||
if xobj != nil {
|
||||
return xobj == yobj
|
||||
}
|
||||
if yobj != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(adonovan): audit: we cannot assume these ast.Exprs
|
||||
// contain non-nil pointers. e.g. ImportSpec.Name may be a
|
||||
// nil *ast.Ident.
|
||||
|
||||
if reflect.TypeOf(x) != reflect.TypeOf(y) {
|
||||
return false
|
||||
}
|
||||
switch x := x.(type) {
|
||||
case *ast.Ident:
|
||||
log.Fatalf("unexpected Ident: %s", astString(tr.fset, x))
|
||||
|
||||
case *ast.BasicLit:
|
||||
y := y.(*ast.BasicLit)
|
||||
xval := exact.MakeFromLiteral(x.Value, x.Kind, 0)
|
||||
yval := exact.MakeFromLiteral(y.Value, y.Kind, 0)
|
||||
return exact.Compare(xval, token.EQL, yval)
|
||||
|
||||
case *ast.FuncLit:
|
||||
// func literals (and thus statement syntax) never match.
|
||||
return false
|
||||
|
||||
case *ast.CompositeLit:
|
||||
y := y.(*ast.CompositeLit)
|
||||
return (x.Type == nil) == (y.Type == nil) &&
|
||||
(x.Type == nil || tr.matchType(x.Type, y.Type)) &&
|
||||
tr.matchExprs(x.Elts, y.Elts)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
y := y.(*ast.SelectorExpr)
|
||||
return tr.matchSelectorExpr(x, y) &&
|
||||
tr.info.Selections[x].Obj() == tr.info.Selections[y].Obj()
|
||||
|
||||
case *ast.IndexExpr:
|
||||
y := y.(*ast.IndexExpr)
|
||||
return tr.matchExpr(x.X, y.X) &&
|
||||
tr.matchExpr(x.Index, y.Index)
|
||||
|
||||
case *ast.SliceExpr:
|
||||
y := y.(*ast.SliceExpr)
|
||||
return tr.matchExpr(x.X, y.X) &&
|
||||
tr.matchExpr(x.Low, y.Low) &&
|
||||
tr.matchExpr(x.High, y.High) &&
|
||||
tr.matchExpr(x.Max, y.Max) &&
|
||||
x.Slice3 == y.Slice3
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
y := y.(*ast.TypeAssertExpr)
|
||||
return tr.matchExpr(x.X, y.X) &&
|
||||
tr.matchType(x.Type, y.Type)
|
||||
|
||||
case *ast.CallExpr:
|
||||
y := y.(*ast.CallExpr)
|
||||
match := tr.matchExpr // function call
|
||||
if tr.info.Types[x.Fun].IsType() {
|
||||
match = tr.matchType // type conversion
|
||||
}
|
||||
return x.Ellipsis.IsValid() == y.Ellipsis.IsValid() &&
|
||||
match(x.Fun, y.Fun) &&
|
||||
tr.matchExprs(x.Args, y.Args)
|
||||
|
||||
case *ast.StarExpr:
|
||||
y := y.(*ast.StarExpr)
|
||||
return tr.matchExpr(x.X, y.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
y := y.(*ast.UnaryExpr)
|
||||
return x.Op == y.Op &&
|
||||
tr.matchExpr(x.X, y.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
y := y.(*ast.BinaryExpr)
|
||||
return x.Op == y.Op &&
|
||||
tr.matchExpr(x.X, y.X) &&
|
||||
tr.matchExpr(x.Y, y.Y)
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
y := y.(*ast.KeyValueExpr)
|
||||
return tr.matchExpr(x.Key, y.Key) &&
|
||||
tr.matchExpr(x.Value, y.Value)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unhandled AST node type: %T", x))
|
||||
}
|
||||
|
||||
func (tr *Transformer) matchExprs(xx, yy []ast.Expr) bool {
|
||||
if len(xx) != len(yy) {
|
||||
return false
|
||||
}
|
||||
for i := range xx {
|
||||
if !tr.matchExpr(xx[i], yy[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// matchType reports whether the two type ASTs denote identical types.
|
||||
func (tr *Transformer) matchType(x, y ast.Expr) bool {
|
||||
tx := tr.info.Types[x].Type
|
||||
ty := tr.info.Types[y].Type
|
||||
return types.Identical(tx, ty)
|
||||
}
|
||||
|
||||
func (tr *Transformer) wildcardObj(x ast.Expr) (*types.Var, bool) {
|
||||
if x, ok := x.(*ast.Ident); ok && x != nil && tr.allowWildcards {
|
||||
if xobj, ok := tr.info.Uses[x].(*types.Var); ok && tr.wildcards[xobj] {
|
||||
return xobj, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (tr *Transformer) matchSelectorExpr(x, y *ast.SelectorExpr) bool {
|
||||
if xobj, ok := tr.wildcardObj(x.X); ok {
|
||||
field := x.Sel.Name
|
||||
yt := tr.info.TypeOf(y.X)
|
||||
o, _, _ := types.LookupFieldOrMethod(yt, true, tr.currentPkg, field)
|
||||
if o != nil {
|
||||
tr.env[xobj.Name()] = y.X // record binding
|
||||
return true
|
||||
}
|
||||
}
|
||||
return tr.matchExpr(x.X, y.X)
|
||||
}
|
||||
|
||||
func (tr *Transformer) matchWildcard(xobj *types.Var, y ast.Expr) bool {
|
||||
name := xobj.Name()
|
||||
|
||||
if tr.verbose {
|
||||
fmt.Fprintf(os.Stderr, "%s: wildcard %s -> %s?: ",
|
||||
tr.fset.Position(y.Pos()), name, astString(tr.fset, y))
|
||||
}
|
||||
|
||||
// Check that y is assignable to the declared type of the param.
|
||||
yt := tr.info.TypeOf(y)
|
||||
if yt == nil {
|
||||
// y has no type.
|
||||
// Perhaps it is an *ast.Ellipsis in [...]T{}, or
|
||||
// an *ast.KeyValueExpr in T{k: v}.
|
||||
// Clearly these pseudo-expressions cannot match a
|
||||
// wildcard, but it would nice if we had a way to ignore
|
||||
// the difference between T{v} and T{k:v} for structs.
|
||||
return false
|
||||
}
|
||||
if !types.AssignableTo(yt, xobj.Type()) {
|
||||
if tr.verbose {
|
||||
fmt.Fprintf(os.Stderr, "%s not assignable to %s\n", yt, xobj.Type())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A wildcard matches any expression.
|
||||
// If it appears multiple times in the pattern, it must match
|
||||
// the same expression each time.
|
||||
if old, ok := tr.env[name]; ok {
|
||||
// found existing binding
|
||||
tr.allowWildcards = false
|
||||
r := tr.matchExpr(old, y)
|
||||
if tr.verbose {
|
||||
fmt.Fprintf(os.Stderr, "%t secondary match, primary was %s\n",
|
||||
r, astString(tr.fset, old))
|
||||
}
|
||||
tr.allowWildcards = true
|
||||
return r
|
||||
}
|
||||
|
||||
if tr.verbose {
|
||||
fmt.Fprintf(os.Stderr, "primary match\n")
|
||||
}
|
||||
|
||||
tr.env[name] = y // record binding
|
||||
return true
|
||||
}
|
||||
|
||||
// -- utilities --------------------------------------------------------
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
||||
|
||||
// isRef returns the object referred to by this (possibly qualified)
|
||||
// identifier, or nil if the node is not a referring identifier.
|
||||
func isRef(n ast.Node, info *types.Info) types.Object {
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
return info.Uses[n]
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
if _, ok := info.Selections[n]; !ok {
|
||||
// qualified ident
|
||||
return info.Uses[n.Sel]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
401
vendor/golang.org/x/tools/refactor/eg/rewrite.go
generated
vendored
Normal file
401
vendor/golang.org/x/tools/refactor/eg/rewrite.go
generated
vendored
Normal file
@@ -0,0 +1,401 @@
|
||||
// Copyright 2014 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 eg
|
||||
|
||||
// This file defines the AST rewriting pass.
|
||||
// Most of it was plundered directly from
|
||||
// $GOROOT/src/cmd/gofmt/rewrite.go (after convergent evolution).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// transformItem takes a reflect.Value representing a variable of type ast.Node
|
||||
// transforms its child elements recursively with apply, and then transforms the
|
||||
// actual element if it contains an expression.
|
||||
func (tr *Transformer) transformItem(rv reflect.Value) (reflect.Value, bool, map[string]ast.Expr) {
|
||||
// don't bother if val is invalid to start with
|
||||
if !rv.IsValid() {
|
||||
return reflect.Value{}, false, nil
|
||||
}
|
||||
|
||||
rv, changed, newEnv := tr.apply(tr.transformItem, rv)
|
||||
|
||||
e := rvToExpr(rv)
|
||||
if e == nil {
|
||||
return rv, changed, newEnv
|
||||
}
|
||||
|
||||
savedEnv := tr.env
|
||||
tr.env = make(map[string]ast.Expr) // inefficient! Use a slice of k/v pairs
|
||||
|
||||
if tr.matchExpr(tr.before, e) {
|
||||
if tr.verbose {
|
||||
fmt.Fprintf(os.Stderr, "%s matches %s",
|
||||
astString(tr.fset, tr.before), astString(tr.fset, e))
|
||||
if len(tr.env) > 0 {
|
||||
fmt.Fprintf(os.Stderr, " with:")
|
||||
for name, ast := range tr.env {
|
||||
fmt.Fprintf(os.Stderr, " %s->%s",
|
||||
name, astString(tr.fset, ast))
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
}
|
||||
tr.nsubsts++
|
||||
|
||||
// Clone the replacement tree, performing parameter substitution.
|
||||
// We update all positions to n.Pos() to aid comment placement.
|
||||
rv = tr.subst(tr.env, reflect.ValueOf(tr.after),
|
||||
reflect.ValueOf(e.Pos()))
|
||||
changed = true
|
||||
newEnv = tr.env
|
||||
}
|
||||
tr.env = savedEnv
|
||||
|
||||
return rv, changed, newEnv
|
||||
}
|
||||
|
||||
// Transform applies the transformation to the specified parsed file,
|
||||
// whose type information is supplied in info, and returns the number
|
||||
// of replacements that were made.
|
||||
//
|
||||
// It mutates the AST in place (the identity of the root node is
|
||||
// unchanged), and may add nodes for which no type information is
|
||||
// available in info.
|
||||
//
|
||||
// Derived from rewriteFile in $GOROOT/src/cmd/gofmt/rewrite.go.
|
||||
//
|
||||
func (tr *Transformer) Transform(info *types.Info, pkg *types.Package, file *ast.File) int {
|
||||
if !tr.seenInfos[info] {
|
||||
tr.seenInfos[info] = true
|
||||
mergeTypeInfo(tr.info, info)
|
||||
}
|
||||
tr.currentPkg = pkg
|
||||
tr.nsubsts = 0
|
||||
|
||||
if tr.verbose {
|
||||
fmt.Fprintf(os.Stderr, "before: %s\n", astString(tr.fset, tr.before))
|
||||
fmt.Fprintf(os.Stderr, "after: %s\n", astString(tr.fset, tr.after))
|
||||
fmt.Fprintf(os.Stderr, "afterStmts: %s\n", tr.afterStmts)
|
||||
}
|
||||
|
||||
o, changed, _ := tr.apply(tr.transformItem, reflect.ValueOf(file))
|
||||
if changed {
|
||||
panic("BUG")
|
||||
}
|
||||
file2 := o.Interface().(*ast.File)
|
||||
|
||||
// By construction, the root node is unchanged.
|
||||
if file != file2 {
|
||||
panic("BUG")
|
||||
}
|
||||
|
||||
// Add any necessary imports.
|
||||
// TODO(adonovan): remove no-longer needed imports too.
|
||||
if tr.nsubsts > 0 {
|
||||
pkgs := make(map[string]*types.Package)
|
||||
for obj := range tr.importedObjs {
|
||||
pkgs[obj.Pkg().Path()] = obj.Pkg()
|
||||
}
|
||||
|
||||
for _, imp := range file.Imports {
|
||||
path, _ := strconv.Unquote(imp.Path.Value)
|
||||
delete(pkgs, path)
|
||||
}
|
||||
delete(pkgs, pkg.Path()) // don't import self
|
||||
|
||||
// NB: AddImport may completely replace the AST!
|
||||
// It thus renders info and tr.info no longer relevant to file.
|
||||
var paths []string
|
||||
for path := range pkgs {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
for _, path := range paths {
|
||||
astutil.AddImport(tr.fset, file, path)
|
||||
}
|
||||
}
|
||||
|
||||
tr.currentPkg = nil
|
||||
|
||||
return tr.nsubsts
|
||||
}
|
||||
|
||||
// setValue is a wrapper for x.SetValue(y); it protects
|
||||
// the caller from panics if x cannot be changed to y.
|
||||
func setValue(x, y reflect.Value) {
|
||||
// don't bother if y is invalid to start with
|
||||
if !y.IsValid() {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
if s, ok := x.(string); ok &&
|
||||
(strings.Contains(s, "type mismatch") || strings.Contains(s, "not assignable")) {
|
||||
// x cannot be set to y - ignore this rewrite
|
||||
return
|
||||
}
|
||||
panic(x)
|
||||
}
|
||||
}()
|
||||
x.Set(y)
|
||||
}
|
||||
|
||||
// Values/types for special cases.
|
||||
var (
|
||||
objectPtrNil = reflect.ValueOf((*ast.Object)(nil))
|
||||
scopePtrNil = reflect.ValueOf((*ast.Scope)(nil))
|
||||
|
||||
identType = reflect.TypeOf((*ast.Ident)(nil))
|
||||
selectorExprType = reflect.TypeOf((*ast.SelectorExpr)(nil))
|
||||
objectPtrType = reflect.TypeOf((*ast.Object)(nil))
|
||||
statementType = reflect.TypeOf((*ast.Stmt)(nil)).Elem()
|
||||
positionType = reflect.TypeOf(token.NoPos)
|
||||
scopePtrType = reflect.TypeOf((*ast.Scope)(nil))
|
||||
)
|
||||
|
||||
// apply replaces each AST field x in val with f(x), returning val.
|
||||
// To avoid extra conversions, f operates on the reflect.Value form.
|
||||
// f takes a reflect.Value representing the variable to modify of type ast.Node.
|
||||
// It returns a reflect.Value containing the transformed value of type ast.Node,
|
||||
// whether any change was made, and a map of identifiers to ast.Expr (so we can
|
||||
// do contextually correct substitutions in the parent statements).
|
||||
func (tr *Transformer) apply(f func(reflect.Value) (reflect.Value, bool, map[string]ast.Expr), val reflect.Value) (reflect.Value, bool, map[string]ast.Expr) {
|
||||
if !val.IsValid() {
|
||||
return reflect.Value{}, false, nil
|
||||
}
|
||||
|
||||
// *ast.Objects introduce cycles and are likely incorrect after
|
||||
// rewrite; don't follow them but replace with nil instead
|
||||
if val.Type() == objectPtrType {
|
||||
return objectPtrNil, false, nil
|
||||
}
|
||||
|
||||
// similarly for scopes: they are likely incorrect after a rewrite;
|
||||
// replace them with nil
|
||||
if val.Type() == scopePtrType {
|
||||
return scopePtrNil, false, nil
|
||||
}
|
||||
|
||||
switch v := reflect.Indirect(val); v.Kind() {
|
||||
case reflect.Slice:
|
||||
// no possible rewriting of statements.
|
||||
if v.Type().Elem() != statementType {
|
||||
changed := false
|
||||
var envp map[string]ast.Expr
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
e := v.Index(i)
|
||||
o, localchanged, env := f(e)
|
||||
if localchanged {
|
||||
changed = true
|
||||
// we clobber envp here,
|
||||
// which means if we have two sucessive
|
||||
// replacements inside the same statement
|
||||
// we will only generate the setup for one of them.
|
||||
envp = env
|
||||
}
|
||||
setValue(e, o)
|
||||
}
|
||||
return val, changed, envp
|
||||
}
|
||||
|
||||
// statements are rewritten.
|
||||
var out []ast.Stmt
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
e := v.Index(i)
|
||||
o, changed, env := f(e)
|
||||
if changed {
|
||||
for _, s := range tr.afterStmts {
|
||||
t := tr.subst(env, reflect.ValueOf(s), reflect.Value{}).Interface()
|
||||
out = append(out, t.(ast.Stmt))
|
||||
}
|
||||
}
|
||||
setValue(e, o)
|
||||
out = append(out, e.Interface().(ast.Stmt))
|
||||
}
|
||||
return reflect.ValueOf(out), false, nil
|
||||
case reflect.Struct:
|
||||
changed := false
|
||||
var envp map[string]ast.Expr
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
e := v.Field(i)
|
||||
o, localchanged, env := f(e)
|
||||
if localchanged {
|
||||
changed = true
|
||||
envp = env
|
||||
}
|
||||
setValue(e, o)
|
||||
}
|
||||
return val, changed, envp
|
||||
case reflect.Interface:
|
||||
e := v.Elem()
|
||||
o, changed, env := f(e)
|
||||
setValue(v, o)
|
||||
return val, changed, env
|
||||
}
|
||||
return val, false, nil
|
||||
}
|
||||
|
||||
// subst returns a copy of (replacement) pattern with values from env
|
||||
// substituted in place of wildcards and pos used as the position of
|
||||
// tokens from the pattern. if env == nil, subst returns a copy of
|
||||
// pattern and doesn't change the line number information.
|
||||
func (tr *Transformer) subst(env map[string]ast.Expr, pattern, pos reflect.Value) reflect.Value {
|
||||
if !pattern.IsValid() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
// *ast.Objects introduce cycles and are likely incorrect after
|
||||
// rewrite; don't follow them but replace with nil instead
|
||||
if pattern.Type() == objectPtrType {
|
||||
return objectPtrNil
|
||||
}
|
||||
|
||||
// similarly for scopes: they are likely incorrect after a rewrite;
|
||||
// replace them with nil
|
||||
if pattern.Type() == scopePtrType {
|
||||
return scopePtrNil
|
||||
}
|
||||
|
||||
// Wildcard gets replaced with map value.
|
||||
if env != nil && pattern.Type() == identType {
|
||||
id := pattern.Interface().(*ast.Ident)
|
||||
if old, ok := env[id.Name]; ok {
|
||||
return tr.subst(nil, reflect.ValueOf(old), reflect.Value{})
|
||||
}
|
||||
}
|
||||
|
||||
// Emit qualified identifiers in the pattern by appropriate
|
||||
// (possibly qualified) identifier in the input.
|
||||
//
|
||||
// The template cannot contain dot imports, so all identifiers
|
||||
// for imported objects are explicitly qualified.
|
||||
//
|
||||
// We assume (unsoundly) that there are no dot or named
|
||||
// imports in the input code, nor are any imported package
|
||||
// names shadowed, so the usual normal qualified identifier
|
||||
// syntax may be used.
|
||||
// TODO(adonovan): fix: avoid this assumption.
|
||||
//
|
||||
// A refactoring may be applied to a package referenced by the
|
||||
// template. Objects belonging to the current package are
|
||||
// denoted by unqualified identifiers.
|
||||
//
|
||||
if tr.importedObjs != nil && pattern.Type() == selectorExprType {
|
||||
obj := isRef(pattern.Interface().(*ast.SelectorExpr), tr.info)
|
||||
if obj != nil {
|
||||
if sel, ok := tr.importedObjs[obj]; ok {
|
||||
var id ast.Expr
|
||||
if obj.Pkg() == tr.currentPkg {
|
||||
id = sel.Sel // unqualified
|
||||
} else {
|
||||
id = sel // pkg-qualified
|
||||
}
|
||||
|
||||
// Return a clone of id.
|
||||
saved := tr.importedObjs
|
||||
tr.importedObjs = nil // break cycle
|
||||
r := tr.subst(nil, reflect.ValueOf(id), pos)
|
||||
tr.importedObjs = saved
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pos.IsValid() && pattern.Type() == positionType {
|
||||
// use new position only if old position was valid in the first place
|
||||
if old := pattern.Interface().(token.Pos); !old.IsValid() {
|
||||
return pattern
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// Otherwise copy.
|
||||
switch p := pattern; p.Kind() {
|
||||
case reflect.Slice:
|
||||
v := reflect.MakeSlice(p.Type(), p.Len(), p.Len())
|
||||
for i := 0; i < p.Len(); i++ {
|
||||
v.Index(i).Set(tr.subst(env, p.Index(i), pos))
|
||||
}
|
||||
return v
|
||||
|
||||
case reflect.Struct:
|
||||
v := reflect.New(p.Type()).Elem()
|
||||
for i := 0; i < p.NumField(); i++ {
|
||||
v.Field(i).Set(tr.subst(env, p.Field(i), pos))
|
||||
}
|
||||
return v
|
||||
|
||||
case reflect.Ptr:
|
||||
v := reflect.New(p.Type()).Elem()
|
||||
if elem := p.Elem(); elem.IsValid() {
|
||||
v.Set(tr.subst(env, elem, pos).Addr())
|
||||
}
|
||||
|
||||
// Duplicate type information for duplicated ast.Expr.
|
||||
// All ast.Node implementations are *structs,
|
||||
// so this case catches them all.
|
||||
if e := rvToExpr(v); e != nil {
|
||||
updateTypeInfo(tr.info, e, p.Interface().(ast.Expr))
|
||||
}
|
||||
return v
|
||||
|
||||
case reflect.Interface:
|
||||
v := reflect.New(p.Type()).Elem()
|
||||
if elem := p.Elem(); elem.IsValid() {
|
||||
v.Set(tr.subst(env, elem, pos))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
return pattern
|
||||
}
|
||||
|
||||
// -- utilities -------------------------------------------------------
|
||||
|
||||
func rvToExpr(rv reflect.Value) ast.Expr {
|
||||
if rv.CanInterface() {
|
||||
if e, ok := rv.Interface().(ast.Expr); ok {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateTypeInfo duplicates type information for the existing AST old
|
||||
// so that it also applies to duplicated AST new.
|
||||
func updateTypeInfo(info *types.Info, new, old ast.Expr) {
|
||||
switch new := new.(type) {
|
||||
case *ast.Ident:
|
||||
orig := old.(*ast.Ident)
|
||||
if obj, ok := info.Defs[orig]; ok {
|
||||
info.Defs[new] = obj
|
||||
}
|
||||
if obj, ok := info.Uses[orig]; ok {
|
||||
info.Uses[new] = obj
|
||||
}
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
orig := old.(*ast.SelectorExpr)
|
||||
if sel, ok := info.Selections[orig]; ok {
|
||||
info.Selections[new] = sel
|
||||
}
|
||||
}
|
||||
|
||||
if tv, ok := info.Types[old]; ok {
|
||||
info.Types[new] = tv
|
||||
}
|
||||
}
|
13
vendor/golang.org/x/tools/refactor/eg/testdata/A.template
generated
vendored
Normal file
13
vendor/golang.org/x/tools/refactor/eg/testdata/A.template
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build ignore
|
||||
|
||||
package template
|
||||
|
||||
// Basic test of type-aware expression refactoring.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func before(s string) error { return fmt.Errorf("%s", s) }
|
||||
func after(s string) error { return errors.New(s) }
|
51
vendor/golang.org/x/tools/refactor/eg/testdata/A1.go
generated
vendored
Normal file
51
vendor/golang.org/x/tools/refactor/eg/testdata/A1.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// +build ignore
|
||||
|
||||
package A1
|
||||
|
||||
import (
|
||||
. "fmt"
|
||||
myfmt "fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func example(n int) {
|
||||
x := "foo" + strings.Repeat("\t", n)
|
||||
// Match, despite named import.
|
||||
myfmt.Errorf("%s", x)
|
||||
|
||||
// Match, despite dot import.
|
||||
Errorf("%s", x)
|
||||
|
||||
// Match: multiple matches in same function are possible.
|
||||
myfmt.Errorf("%s", x)
|
||||
|
||||
// No match: wildcarded operand has the wrong type.
|
||||
myfmt.Errorf("%s", 3)
|
||||
|
||||
// No match: function operand doesn't match.
|
||||
myfmt.Printf("%s", x)
|
||||
|
||||
// No match again, dot import.
|
||||
Printf("%s", x)
|
||||
|
||||
// Match.
|
||||
myfmt.Fprint(os.Stderr, myfmt.Errorf("%s", x+"foo"))
|
||||
|
||||
// No match: though this literally matches the template,
|
||||
// fmt doesn't resolve to a package here.
|
||||
var fmt struct{ Errorf func(string, string) }
|
||||
fmt.Errorf("%s", x)
|
||||
|
||||
// Recursive matching:
|
||||
|
||||
// Match: both matches are well-typed, so both succeed.
|
||||
myfmt.Errorf("%s", myfmt.Errorf("%s", x+"foo").Error())
|
||||
|
||||
// Outer match succeeds, inner doesn't: 3 has wrong type.
|
||||
myfmt.Errorf("%s", myfmt.Errorf("%s", 3).Error())
|
||||
|
||||
// Inner match succeeds, outer doesn't: the inner replacement
|
||||
// has the wrong type (error not string).
|
||||
myfmt.Errorf("%s", myfmt.Errorf("%s", x+"foo"))
|
||||
}
|
52
vendor/golang.org/x/tools/refactor/eg/testdata/A1.golden
generated
vendored
Normal file
52
vendor/golang.org/x/tools/refactor/eg/testdata/A1.golden
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// +build ignore
|
||||
|
||||
package A1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "fmt"
|
||||
myfmt "fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func example(n int) {
|
||||
x := "foo" + strings.Repeat("\t", n)
|
||||
// Match, despite named import.
|
||||
errors.New(x)
|
||||
|
||||
// Match, despite dot import.
|
||||
errors.New(x)
|
||||
|
||||
// Match: multiple matches in same function are possible.
|
||||
errors.New(x)
|
||||
|
||||
// No match: wildcarded operand has the wrong type.
|
||||
myfmt.Errorf("%s", 3)
|
||||
|
||||
// No match: function operand doesn't match.
|
||||
myfmt.Printf("%s", x)
|
||||
|
||||
// No match again, dot import.
|
||||
Printf("%s", x)
|
||||
|
||||
// Match.
|
||||
myfmt.Fprint(os.Stderr, errors.New(x+"foo"))
|
||||
|
||||
// No match: though this literally matches the template,
|
||||
// fmt doesn't resolve to a package here.
|
||||
var fmt struct{ Errorf func(string, string) }
|
||||
fmt.Errorf("%s", x)
|
||||
|
||||
// Recursive matching:
|
||||
|
||||
// Match: both matches are well-typed, so both succeed.
|
||||
errors.New(errors.New(x + "foo").Error())
|
||||
|
||||
// Outer match succeeds, inner doesn't: 3 has wrong type.
|
||||
errors.New(myfmt.Errorf("%s", 3).Error())
|
||||
|
||||
// Inner match succeeds, outer doesn't: the inner replacement
|
||||
// has the wrong type (error not string).
|
||||
myfmt.Errorf("%s", errors.New(x+"foo"))
|
||||
}
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/A2.go
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/A2.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package A2
|
||||
|
||||
// This refactoring causes addition of "errors" import.
|
||||
// TODO(adonovan): fix: it should also remove "fmt".
|
||||
|
||||
import myfmt "fmt"
|
||||
|
||||
func example(n int) {
|
||||
myfmt.Errorf("%s", "")
|
||||
}
|
15
vendor/golang.org/x/tools/refactor/eg/testdata/A2.golden
generated
vendored
Normal file
15
vendor/golang.org/x/tools/refactor/eg/testdata/A2.golden
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build ignore
|
||||
|
||||
package A2
|
||||
|
||||
// This refactoring causes addition of "errors" import.
|
||||
// TODO(adonovan): fix: it should also remove "fmt".
|
||||
|
||||
import (
|
||||
"errors"
|
||||
myfmt "fmt"
|
||||
)
|
||||
|
||||
func example(n int) {
|
||||
errors.New("")
|
||||
}
|
9
vendor/golang.org/x/tools/refactor/eg/testdata/B.template
generated
vendored
Normal file
9
vendor/golang.org/x/tools/refactor/eg/testdata/B.template
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package template
|
||||
|
||||
// Basic test of expression refactoring.
|
||||
// (Types are not important in this case; it could be done with gofmt -r.)
|
||||
|
||||
import "time"
|
||||
|
||||
func before(t time.Time) time.Duration { return time.Now().Sub(t) }
|
||||
func after(t time.Time) time.Duration { return time.Since(t) }
|
17
vendor/golang.org/x/tools/refactor/eg/testdata/B1.go
generated
vendored
Normal file
17
vendor/golang.org/x/tools/refactor/eg/testdata/B1.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build ignore
|
||||
|
||||
package B1
|
||||
|
||||
import "time"
|
||||
|
||||
var startup = time.Now()
|
||||
|
||||
func example() time.Duration {
|
||||
before := time.Now()
|
||||
time.Sleep(1)
|
||||
return time.Now().Sub(before)
|
||||
}
|
||||
|
||||
func msSinceStartup() int64 {
|
||||
return int64(time.Now().Sub(startup) / time.Millisecond)
|
||||
}
|
17
vendor/golang.org/x/tools/refactor/eg/testdata/B1.golden
generated
vendored
Normal file
17
vendor/golang.org/x/tools/refactor/eg/testdata/B1.golden
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build ignore
|
||||
|
||||
package B1
|
||||
|
||||
import "time"
|
||||
|
||||
var startup = time.Now()
|
||||
|
||||
func example() time.Duration {
|
||||
before := time.Now()
|
||||
time.Sleep(1)
|
||||
return time.Since(before)
|
||||
}
|
||||
|
||||
func msSinceStartup() int64 {
|
||||
return int64(time.Since(startup) / time.Millisecond)
|
||||
}
|
10
vendor/golang.org/x/tools/refactor/eg/testdata/C.template
generated
vendored
Normal file
10
vendor/golang.org/x/tools/refactor/eg/testdata/C.template
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package template
|
||||
|
||||
// Test of repeated use of wildcard in pattern.
|
||||
|
||||
// NB: multiple patterns would be required to handle variants such as
|
||||
// s[:len(s)], s[x:len(s)], etc, since a wildcard can't match nothing at all.
|
||||
// TODO(adonovan): support multiple templates in a single pass.
|
||||
|
||||
func before(s string) string { return s[:len(s)] }
|
||||
func after(s string) string { return s }
|
22
vendor/golang.org/x/tools/refactor/eg/testdata/C1.go
generated
vendored
Normal file
22
vendor/golang.org/x/tools/refactor/eg/testdata/C1.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build ignore
|
||||
|
||||
package C1
|
||||
|
||||
import "strings"
|
||||
|
||||
func example() {
|
||||
x := "foo"
|
||||
println(x[:len(x)])
|
||||
|
||||
// Match, but the transformation is not sound w.r.t. possible side effects.
|
||||
println(strings.Repeat("*", 3)[:len(strings.Repeat("*", 3))])
|
||||
|
||||
// No match, since second use of wildcard doesn't match first.
|
||||
println(strings.Repeat("*", 3)[:len(strings.Repeat("*", 2))])
|
||||
|
||||
// Recursive match demonstrating bottom-up rewrite:
|
||||
// only after the inner replacement occurs does the outer syntax match.
|
||||
println((x[:len(x)])[:len(x[:len(x)])])
|
||||
// -> (x[:len(x)])
|
||||
// -> x
|
||||
}
|
22
vendor/golang.org/x/tools/refactor/eg/testdata/C1.golden
generated
vendored
Normal file
22
vendor/golang.org/x/tools/refactor/eg/testdata/C1.golden
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build ignore
|
||||
|
||||
package C1
|
||||
|
||||
import "strings"
|
||||
|
||||
func example() {
|
||||
x := "foo"
|
||||
println(x)
|
||||
|
||||
// Match, but the transformation is not sound w.r.t. possible side effects.
|
||||
println(strings.Repeat("*", 3))
|
||||
|
||||
// No match, since second use of wildcard doesn't match first.
|
||||
println(strings.Repeat("*", 3)[:len(strings.Repeat("*", 2))])
|
||||
|
||||
// Recursive match demonstrating bottom-up rewrite:
|
||||
// only after the inner replacement occurs does the outer syntax match.
|
||||
println(x)
|
||||
// -> (x[:len(x)])
|
||||
// -> x
|
||||
}
|
8
vendor/golang.org/x/tools/refactor/eg/testdata/D.template
generated
vendored
Normal file
8
vendor/golang.org/x/tools/refactor/eg/testdata/D.template
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package template
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Test of semantic (not syntactic) matching of basic literals.
|
||||
|
||||
func before() (int, error) { return fmt.Println(123, "a") }
|
||||
func after() (int, error) { return fmt.Println(456, "!") }
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/D1.go
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/D1.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package D1
|
||||
|
||||
import "fmt"
|
||||
|
||||
func example() {
|
||||
fmt.Println(123, "a") // match
|
||||
fmt.Println(0x7b, `a`) // match
|
||||
fmt.Println(0173, "\x61") // match
|
||||
fmt.Println(100+20+3, "a"+"") // no match: constant expressions, but not basic literals
|
||||
}
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/D1.golden
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/D1.golden
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package D1
|
||||
|
||||
import "fmt"
|
||||
|
||||
func example() {
|
||||
fmt.Println(456, "!") // match
|
||||
fmt.Println(456, "!") // match
|
||||
fmt.Println(456, "!") // match
|
||||
fmt.Println(100+20+3, "a"+"") // no match: constant expressions, but not basic literals
|
||||
}
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/E.template
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/E.template
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Replace call to void function by call to non-void function.
|
||||
|
||||
func before(x interface{}) { log.Fatal(x) }
|
||||
func after(x interface{}) { fmt.Fprintf(os.Stderr, "warning: %v", x) }
|
9
vendor/golang.org/x/tools/refactor/eg/testdata/E1.go
generated
vendored
Normal file
9
vendor/golang.org/x/tools/refactor/eg/testdata/E1.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build ignore
|
||||
|
||||
package E1
|
||||
|
||||
import "log"
|
||||
|
||||
func example() {
|
||||
log.Fatal("oops") // match
|
||||
}
|
13
vendor/golang.org/x/tools/refactor/eg/testdata/E1.golden
generated
vendored
Normal file
13
vendor/golang.org/x/tools/refactor/eg/testdata/E1.golden
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build ignore
|
||||
|
||||
package E1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func example() {
|
||||
fmt.Fprintf(os.Stderr, "warning: %v", "oops") // match
|
||||
}
|
8
vendor/golang.org/x/tools/refactor/eg/testdata/F.template
generated
vendored
Normal file
8
vendor/golang.org/x/tools/refactor/eg/testdata/F.template
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package templates
|
||||
|
||||
// Test
|
||||
|
||||
import "sync"
|
||||
|
||||
func before(s sync.RWMutex) { s.Lock() }
|
||||
func after(s sync.RWMutex) { s.RLock() }
|
48
vendor/golang.org/x/tools/refactor/eg/testdata/F1.go
generated
vendored
Normal file
48
vendor/golang.org/x/tools/refactor/eg/testdata/F1.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// +build ignore
|
||||
|
||||
package F1
|
||||
|
||||
import "sync"
|
||||
|
||||
func example(n int) {
|
||||
var x struct {
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
var y struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type l struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
var z struct {
|
||||
l
|
||||
}
|
||||
|
||||
var a struct {
|
||||
*l
|
||||
}
|
||||
|
||||
var b struct{ Lock func() }
|
||||
|
||||
// Match
|
||||
x.mutex.Lock()
|
||||
|
||||
// Match
|
||||
y.Lock()
|
||||
|
||||
// Match indirect
|
||||
z.Lock()
|
||||
|
||||
// Should be no match however currently matches due to:
|
||||
// https://golang.org/issue/8584
|
||||
// Will start failing when this is fixed then just change golden to
|
||||
// No match pointer indirect
|
||||
// a.Lock()
|
||||
a.Lock()
|
||||
|
||||
// No match
|
||||
b.Lock()
|
||||
}
|
48
vendor/golang.org/x/tools/refactor/eg/testdata/F1.golden
generated
vendored
Normal file
48
vendor/golang.org/x/tools/refactor/eg/testdata/F1.golden
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// +build ignore
|
||||
|
||||
package F1
|
||||
|
||||
import "sync"
|
||||
|
||||
func example(n int) {
|
||||
var x struct {
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
var y struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type l struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
var z struct {
|
||||
l
|
||||
}
|
||||
|
||||
var a struct {
|
||||
*l
|
||||
}
|
||||
|
||||
var b struct{ Lock func() }
|
||||
|
||||
// Match
|
||||
x.mutex.RLock()
|
||||
|
||||
// Match
|
||||
y.RLock()
|
||||
|
||||
// Match indirect
|
||||
z.RLock()
|
||||
|
||||
// Should be no match however currently matches due to:
|
||||
// https://golang.org/issue/8584
|
||||
// Will start failing when this is fixed then just change golden to
|
||||
// No match pointer indirect
|
||||
// a.Lock()
|
||||
a.RLock()
|
||||
|
||||
// No match
|
||||
b.Lock()
|
||||
}
|
10
vendor/golang.org/x/tools/refactor/eg/testdata/G.template
generated
vendored
Normal file
10
vendor/golang.org/x/tools/refactor/eg/testdata/G.template
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"go/ast" // defines many unencapsulated structs
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func before(from, to token.Pos) ast.BadExpr { return ast.BadExpr{From: from, To: to} }
|
||||
func after(from, to token.Pos) ast.BadExpr { return ast.BadExpr{from, to} }
|
||||
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/G1.go
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/G1.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package G1
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func example() {
|
||||
_ = ast.BadExpr{From: 123, To: 456} // match
|
||||
_ = ast.BadExpr{123, 456} // no match
|
||||
_ = ast.BadExpr{From: 123} // no match
|
||||
_ = ast.BadExpr{To: 456} // no match
|
||||
}
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/G1.golden
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/G1.golden
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package G1
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func example() {
|
||||
_ = ast.BadExpr{123, 456} // match
|
||||
_ = ast.BadExpr{123, 456} // no match
|
||||
_ = ast.BadExpr{From: 123} // no match
|
||||
_ = ast.BadExpr{To: 456} // no match
|
||||
}
|
9
vendor/golang.org/x/tools/refactor/eg/testdata/H.template
generated
vendored
Normal file
9
vendor/golang.org/x/tools/refactor/eg/testdata/H.template
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"go/ast" // defines many unencapsulated structs
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func before(from, to token.Pos) ast.BadExpr { return ast.BadExpr{from, to} }
|
||||
func after(from, to token.Pos) ast.BadExpr { return ast.BadExpr{From: from, To: to} }
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/H1.go
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/H1.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package H1
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func example() {
|
||||
_ = ast.BadExpr{From: 123, To: 456} // no match
|
||||
_ = ast.BadExpr{123, 456} // match
|
||||
_ = ast.BadExpr{From: 123} // no match
|
||||
_ = ast.BadExpr{To: 456} // no match
|
||||
}
|
12
vendor/golang.org/x/tools/refactor/eg/testdata/H1.golden
generated
vendored
Normal file
12
vendor/golang.org/x/tools/refactor/eg/testdata/H1.golden
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build ignore
|
||||
|
||||
package H1
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func example() {
|
||||
_ = ast.BadExpr{From: 123, To: 456} // no match
|
||||
_ = ast.BadExpr{From: 123, To: 456} // match
|
||||
_ = ast.BadExpr{From: 123} // no match
|
||||
_ = ast.BadExpr{To: 456} // no match
|
||||
}
|
14
vendor/golang.org/x/tools/refactor/eg/testdata/I.template
generated
vendored
Normal file
14
vendor/golang.org/x/tools/refactor/eg/testdata/I.template
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build ignore
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func before(s string) error { return fmt.Errorf("%s", s) }
|
||||
func after(s string) error {
|
||||
n := fmt.Sprintf("error - %s", s)
|
||||
return errors.New(n)
|
||||
}
|
9
vendor/golang.org/x/tools/refactor/eg/testdata/I1.go
generated
vendored
Normal file
9
vendor/golang.org/x/tools/refactor/eg/testdata/I1.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build ignore
|
||||
|
||||
package I1
|
||||
|
||||
import "fmt"
|
||||
|
||||
func example() {
|
||||
_ = fmt.Errorf("%s", "foo")
|
||||
}
|
14
vendor/golang.org/x/tools/refactor/eg/testdata/I1.golden
generated
vendored
Normal file
14
vendor/golang.org/x/tools/refactor/eg/testdata/I1.golden
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build ignore
|
||||
|
||||
package I1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func example() {
|
||||
|
||||
n := fmt.Sprintf("error - %s", "foo")
|
||||
_ = errors.New(n)
|
||||
}
|
11
vendor/golang.org/x/tools/refactor/eg/testdata/J.template
generated
vendored
Normal file
11
vendor/golang.org/x/tools/refactor/eg/testdata/J.template
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build ignore
|
||||
|
||||
package templates
|
||||
|
||||
import ()
|
||||
|
||||
func before(x int) int { return x + x + x }
|
||||
func after(x int) int {
|
||||
temp := x + x
|
||||
return temp + x
|
||||
}
|
10
vendor/golang.org/x/tools/refactor/eg/testdata/J1.go
generated
vendored
Normal file
10
vendor/golang.org/x/tools/refactor/eg/testdata/J1.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// +build ignore
|
||||
|
||||
package I1
|
||||
|
||||
import "fmt"
|
||||
|
||||
func example() {
|
||||
temp := 5
|
||||
fmt.Print(temp + temp + temp)
|
||||
}
|
11
vendor/golang.org/x/tools/refactor/eg/testdata/J1.golden
generated
vendored
Normal file
11
vendor/golang.org/x/tools/refactor/eg/testdata/J1.golden
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build ignore
|
||||
|
||||
package I1
|
||||
|
||||
import "fmt"
|
||||
|
||||
func example() {
|
||||
temp := 5
|
||||
temp := temp + temp
|
||||
fmt.Print(temp + temp)
|
||||
}
|
8
vendor/golang.org/x/tools/refactor/eg/testdata/bad_type.template
generated
vendored
Normal file
8
vendor/golang.org/x/tools/refactor/eg/testdata/bad_type.template
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package template
|
||||
|
||||
// Test in which replacement has a different type.
|
||||
|
||||
const shouldFail = "int is not a safe replacement for string"
|
||||
|
||||
func before() interface{} { return "three" }
|
||||
func after() interface{} { return 3 }
|
15
vendor/golang.org/x/tools/refactor/eg/testdata/expr_type_mismatch.template
generated
vendored
Normal file
15
vendor/golang.org/x/tools/refactor/eg/testdata/expr_type_mismatch.template
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// This test demonstrates a false negative: according to the language
|
||||
// rules this replacement should be ok, but types.Assignable doesn't work
|
||||
// in the expected way (elementwise assignability) for tuples.
|
||||
// Perhaps that's even a type-checker bug?
|
||||
const shouldFail = "(n int, err error) is not a safe replacement for (key interface{}, err error)"
|
||||
|
||||
func before() (interface{}, error) { return x509.ParsePKCS8PrivateKey(nil) }
|
||||
func after() (interface{}, error) { return fmt.Print() }
|
4
vendor/golang.org/x/tools/refactor/eg/testdata/no_after_return.template
generated
vendored
Normal file
4
vendor/golang.org/x/tools/refactor/eg/testdata/no_after_return.template
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
package template
|
||||
|
||||
func before() int { return 0 }
|
||||
func after() int { println(); return 0 }
|
5
vendor/golang.org/x/tools/refactor/eg/testdata/no_before.template
generated
vendored
Normal file
5
vendor/golang.org/x/tools/refactor/eg/testdata/no_before.template
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package template
|
||||
|
||||
const shouldFail = "no 'before' func found in template"
|
||||
|
||||
func Before() {}
|
6
vendor/golang.org/x/tools/refactor/eg/testdata/type_mismatch.template
generated
vendored
Normal file
6
vendor/golang.org/x/tools/refactor/eg/testdata/type_mismatch.template
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package template
|
||||
|
||||
const shouldFail = "different signatures"
|
||||
|
||||
func before() int { return 0 }
|
||||
func after() string { return "" }
|
167
vendor/golang.org/x/tools/refactor/importgraph/graph.go
generated
vendored
Normal file
167
vendor/golang.org/x/tools/refactor/importgraph/graph.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2014 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 importgraph computes the forward and reverse import
|
||||
// dependency graphs for all packages in a Go workspace.
|
||||
package importgraph // import "golang.org/x/tools/refactor/importgraph"
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
)
|
||||
|
||||
// A Graph is an import dependency graph, either forward or reverse.
|
||||
//
|
||||
// The graph maps each node (a package import path) to the set of its
|
||||
// successors in the graph. For a forward graph, this is the set of
|
||||
// imported packages (prerequisites); for a reverse graph, it is the set
|
||||
// of importing packages (clients).
|
||||
//
|
||||
// Graph construction inspects all imports in each package's directory,
|
||||
// including those in _test.go files, so the resulting graph may be cyclic.
|
||||
type Graph map[string]map[string]bool
|
||||
|
||||
func (g Graph) addEdge(from, to string) {
|
||||
edges := g[from]
|
||||
if edges == nil {
|
||||
edges = make(map[string]bool)
|
||||
g[from] = edges
|
||||
}
|
||||
edges[to] = true
|
||||
}
|
||||
|
||||
// Search returns all the nodes of the graph reachable from
|
||||
// any of the specified roots, by following edges forwards.
|
||||
// Relationally, this is the reflexive transitive closure.
|
||||
func (g Graph) Search(roots ...string) map[string]bool {
|
||||
seen := make(map[string]bool)
|
||||
var visit func(x string)
|
||||
visit = func(x string) {
|
||||
if !seen[x] {
|
||||
seen[x] = true
|
||||
for y := range g[x] {
|
||||
visit(y)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, root := range roots {
|
||||
visit(root)
|
||||
}
|
||||
return seen
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
99
vendor/golang.org/x/tools/refactor/importgraph/graph_test.go
generated
vendored
Normal file
99
vendor/golang.org/x/tools/refactor/importgraph/graph_test.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// Incomplete std lib sources on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package importgraph_test
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
|
||||
_ "crypto/hmac" // just for test, below
|
||||
)
|
||||
|
||||
const this = "golang.org/x/tools/refactor/importgraph"
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
forward, reverse, errors := importgraph.Build(&build.Default)
|
||||
|
||||
// Test direct edges.
|
||||
// We throw in crypto/hmac to prove that external test files
|
||||
// (such as this one) are inspected.
|
||||
for _, p := range []string{"go/build", "testing", "crypto/hmac"} {
|
||||
if !forward[this][p] {
|
||||
t.Errorf("forward[importgraph][%s] not found", p)
|
||||
}
|
||||
if !reverse[p][this] {
|
||||
t.Errorf("reverse[%s][importgraph] not found", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Test non-existent direct edges
|
||||
for _, p := range []string{"errors", "reflect"} {
|
||||
if forward[this][p] {
|
||||
t.Errorf("unexpected: forward[importgraph][%s] found", p)
|
||||
}
|
||||
if reverse[p][this] {
|
||||
t.Errorf("unexpected: reverse[%s][importgraph] found", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Test Search is reflexive.
|
||||
if !forward.Search(this)[this] {
|
||||
t.Errorf("irreflexive: forward.Search(importgraph)[importgraph] not found")
|
||||
}
|
||||
if !reverse.Search(this)[this] {
|
||||
t.Errorf("irrefexive: reverse.Search(importgraph)[importgraph] not found")
|
||||
}
|
||||
|
||||
// Test Search is transitive. (There is no direct edge to these packages.)
|
||||
for _, p := range []string{"errors", "reflect", "unsafe"} {
|
||||
if !forward.Search(this)[p] {
|
||||
t.Errorf("intransitive: forward.Search(importgraph)[%s] not found", p)
|
||||
}
|
||||
if !reverse.Search(p)[this] {
|
||||
t.Errorf("intransitive: reverse.Search(%s)[importgraph] not found", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Test strongly-connected components. Because A's external
|
||||
// test package can depend on B, and vice versa, most of the
|
||||
// standard libraries are mutually dependent when their external
|
||||
// tests are considered.
|
||||
//
|
||||
// For any nodes x, y in the same SCC, y appears in the results
|
||||
// of both forward and reverse searches starting from x
|
||||
if !forward.Search("fmt")["io"] ||
|
||||
!forward.Search("io")["fmt"] ||
|
||||
!reverse.Search("fmt")["io"] ||
|
||||
!reverse.Search("io")["fmt"] {
|
||||
t.Errorf("fmt and io are not mutually reachable despite being in the same SCC")
|
||||
}
|
||||
|
||||
// debugging
|
||||
if false {
|
||||
for path, err := range errors {
|
||||
t.Logf("%s: %s", path, err)
|
||||
}
|
||||
printSorted := func(direction string, g importgraph.Graph, start string) {
|
||||
t.Log(direction)
|
||||
var pkgs []string
|
||||
for pkg := range g.Search(start) {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
sort.Strings(pkgs)
|
||||
for _, pkg := range pkgs {
|
||||
t.Logf("\t%s", pkg)
|
||||
}
|
||||
}
|
||||
printSorted("forward", forward, this)
|
||||
printSorted("reverse", reverse, this)
|
||||
}
|
||||
}
|
858
vendor/golang.org/x/tools/refactor/rename/check.go
generated
vendored
Normal file
858
vendor/golang.org/x/tools/refactor/rename/check.go
generated
vendored
Normal file
@@ -0,0 +1,858 @@
|
||||
// Copyright 2014 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 rename
|
||||
|
||||
// This file defines the safety checks for each kind of renaming.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/refactor/satisfy"
|
||||
)
|
||||
|
||||
// errorf reports an error (e.g. conflict) and prevents file modification.
|
||||
func (r *renamer) errorf(pos token.Pos, format string, args ...interface{}) {
|
||||
r.hadConflicts = true
|
||||
reportError(r.iprog.Fset.Position(pos), fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// check performs safety checks of the renaming of the 'from' object to r.to.
|
||||
func (r *renamer) check(from types.Object) {
|
||||
if r.objsToUpdate[from] {
|
||||
return
|
||||
}
|
||||
r.objsToUpdate[from] = true
|
||||
|
||||
// NB: order of conditions is important.
|
||||
if from_, ok := from.(*types.PkgName); ok {
|
||||
r.checkInFileBlock(from_)
|
||||
} else if from_, ok := from.(*types.Label); ok {
|
||||
r.checkLabel(from_)
|
||||
} else if isPackageLevel(from) {
|
||||
r.checkInPackageBlock(from)
|
||||
} else if v, ok := from.(*types.Var); ok && v.IsField() {
|
||||
r.checkStructField(v)
|
||||
} else if f, ok := from.(*types.Func); ok && recv(f) != nil {
|
||||
r.checkMethod(f)
|
||||
} else if isLocal(from) {
|
||||
r.checkInLocalScope(from)
|
||||
} else {
|
||||
r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n",
|
||||
objectKind(from), from)
|
||||
}
|
||||
}
|
||||
|
||||
// checkInFileBlock performs safety checks for renames of objects in the file block,
|
||||
// i.e. imported package names.
|
||||
func (r *renamer) checkInFileBlock(from *types.PkgName) {
|
||||
// Check import name is not "init".
|
||||
if r.to == "init" {
|
||||
r.errorf(from.Pos(), "%q is not a valid imported package name", r.to)
|
||||
}
|
||||
|
||||
// Check for conflicts between file and package block.
|
||||
if prev := from.Pkg().Scope().Lookup(r.to); prev != nil {
|
||||
r.errorf(from.Pos(), "renaming this %s %q to %q would conflict",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
r.errorf(prev.Pos(), "\twith this package member %s",
|
||||
objectKind(prev))
|
||||
return // since checkInPackageBlock would report redundant errors
|
||||
}
|
||||
|
||||
// Check for conflicts in lexical scope.
|
||||
r.checkInLexicalScope(from, r.packages[from.Pkg()])
|
||||
|
||||
// Finally, modify ImportSpec syntax to add or remove the Name as needed.
|
||||
info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos())
|
||||
if from.Imported().Name() == r.to {
|
||||
// ImportSpec.Name not needed
|
||||
path[1].(*ast.ImportSpec).Name = nil
|
||||
} else {
|
||||
// ImportSpec.Name needed
|
||||
if spec := path[1].(*ast.ImportSpec); spec.Name == nil {
|
||||
spec.Name = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
|
||||
info.Defs[spec.Name] = from
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkInPackageBlock performs safety checks for renames of
|
||||
// func/var/const/type objects in the package block.
|
||||
func (r *renamer) checkInPackageBlock(from types.Object) {
|
||||
// Check that there are no references to the name from another
|
||||
// package if the renaming would make it unexported.
|
||||
if ast.IsExported(from.Name()) && !ast.IsExported(r.to) {
|
||||
for pkg, info := range r.packages {
|
||||
if pkg == from.Pkg() {
|
||||
continue
|
||||
}
|
||||
if id := someUse(info, from); id != nil &&
|
||||
!r.checkExport(id, pkg, from) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info := r.packages[from.Pkg()]
|
||||
|
||||
// Check that in the package block, "init" is a function, and never referenced.
|
||||
if r.to == "init" {
|
||||
kind := objectKind(from)
|
||||
if kind == "func" {
|
||||
// Reject if intra-package references to it exist.
|
||||
for id, obj := range info.Uses {
|
||||
if obj == from {
|
||||
r.errorf(from.Pos(),
|
||||
"renaming this func %q to %q would make it a package initializer",
|
||||
from.Name(), r.to)
|
||||
r.errorf(id.Pos(), "\tbut references to it exist")
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.errorf(from.Pos(), "you cannot have a %s at package level named %q",
|
||||
kind, r.to)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for conflicts between package block and all file blocks.
|
||||
for _, f := range info.Files {
|
||||
fileScope := info.Info.Scopes[f]
|
||||
b, prev := fileScope.LookupParent(r.to, token.NoPos)
|
||||
if b == fileScope {
|
||||
r.errorf(from.Pos(), "renaming this %s %q to %q would conflict",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
r.errorf(prev.Pos(), "\twith this %s",
|
||||
objectKind(prev))
|
||||
return // since checkInPackageBlock would report redundant errors
|
||||
}
|
||||
}
|
||||
|
||||
// Check for conflicts in lexical scope.
|
||||
if from.Exported() {
|
||||
for _, info := range r.packages {
|
||||
r.checkInLexicalScope(from, info)
|
||||
}
|
||||
} else {
|
||||
r.checkInLexicalScope(from, info)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renamer) checkInLocalScope(from types.Object) {
|
||||
info := r.packages[from.Pkg()]
|
||||
|
||||
// Is this object an implicit local var for a type switch?
|
||||
// Each case has its own var, whose position is the decl of y,
|
||||
// but Ident in that decl does not appear in the Uses map.
|
||||
//
|
||||
// switch y := x.(type) { // Defs[Ident(y)] is undefined
|
||||
// case int: print(y) // Implicits[CaseClause(int)] = Var(y_int)
|
||||
// case string: print(y) // Implicits[CaseClause(string)] = Var(y_string)
|
||||
// }
|
||||
//
|
||||
var isCaseVar bool
|
||||
for syntax, obj := range info.Implicits {
|
||||
if _, ok := syntax.(*ast.CaseClause); ok && obj.Pos() == from.Pos() {
|
||||
isCaseVar = true
|
||||
r.check(obj)
|
||||
}
|
||||
}
|
||||
|
||||
r.checkInLexicalScope(from, info)
|
||||
|
||||
// Finally, if this was a type switch, change the variable y.
|
||||
if isCaseVar {
|
||||
_, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos())
|
||||
path[0].(*ast.Ident).Name = r.to // path is [Ident AssignStmt TypeSwitchStmt...]
|
||||
}
|
||||
}
|
||||
|
||||
// checkInLexicalScope performs safety checks that a renaming does not
|
||||
// change the lexical reference structure of the specified package.
|
||||
//
|
||||
// For objects in lexical scope, there are three kinds of conflicts:
|
||||
// same-, sub-, and super-block conflicts. We will illustrate all three
|
||||
// using this example:
|
||||
//
|
||||
// var x int
|
||||
// var z int
|
||||
//
|
||||
// func f(y int) {
|
||||
// print(x)
|
||||
// print(y)
|
||||
// }
|
||||
//
|
||||
// Renaming x to z encounters a SAME-BLOCK CONFLICT, because an object
|
||||
// with the new name already exists, defined in the same lexical block
|
||||
// as the old object.
|
||||
//
|
||||
// Renaming x to y encounters a SUB-BLOCK CONFLICT, because there exists
|
||||
// a reference to x from within (what would become) a hole in its scope.
|
||||
// The definition of y in an (inner) sub-block would cast a shadow in
|
||||
// the scope of the renamed variable.
|
||||
//
|
||||
// Renaming y to x encounters a SUPER-BLOCK CONFLICT. This is the
|
||||
// converse situation: there is an existing definition of the new name
|
||||
// (x) in an (enclosing) super-block, and the renaming would create a
|
||||
// hole in its scope, within which there exist references to it. The
|
||||
// new name casts a shadow in scope of the existing definition of x in
|
||||
// the super-block.
|
||||
//
|
||||
// Removing the old name (and all references to it) is always safe, and
|
||||
// requires no checks.
|
||||
//
|
||||
func (r *renamer) checkInLexicalScope(from types.Object, info *loader.PackageInfo) {
|
||||
b := from.Parent() // the block defining the 'from' object
|
||||
if b != nil {
|
||||
toBlock, to := b.LookupParent(r.to, from.Parent().End())
|
||||
if toBlock == b {
|
||||
// same-block conflict
|
||||
r.errorf(from.Pos(), "renaming this %s %q to %q",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
r.errorf(to.Pos(), "\tconflicts with %s in same block",
|
||||
objectKind(to))
|
||||
return
|
||||
} else if toBlock != nil {
|
||||
// Check for super-block conflict.
|
||||
// The name r.to is defined in a superblock.
|
||||
// Is that name referenced from within this block?
|
||||
forEachLexicalRef(info, to, func(id *ast.Ident, block *types.Scope) bool {
|
||||
_, obj := lexicalLookup(block, from.Name(), id.Pos())
|
||||
if obj == from {
|
||||
// super-block conflict
|
||||
r.errorf(from.Pos(), "renaming this %s %q to %q",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
r.errorf(id.Pos(), "\twould shadow this reference")
|
||||
r.errorf(to.Pos(), "\tto the %s declared here",
|
||||
objectKind(to))
|
||||
return false // stop
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check for sub-block conflict.
|
||||
// Is there an intervening definition of r.to between
|
||||
// the block defining 'from' and some reference to it?
|
||||
forEachLexicalRef(info, from, func(id *ast.Ident, block *types.Scope) bool {
|
||||
// Find the block that defines the found reference.
|
||||
// It may be an ancestor.
|
||||
fromBlock, _ := lexicalLookup(block, from.Name(), id.Pos())
|
||||
|
||||
// See what r.to would resolve to in the same scope.
|
||||
toBlock, to := lexicalLookup(block, r.to, id.Pos())
|
||||
if to != nil {
|
||||
// sub-block conflict
|
||||
if deeper(toBlock, fromBlock) {
|
||||
r.errorf(from.Pos(), "renaming this %s %q to %q",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
r.errorf(id.Pos(), "\twould cause this reference to become shadowed")
|
||||
r.errorf(to.Pos(), "\tby this intervening %s definition",
|
||||
objectKind(to))
|
||||
return false // stop
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Renaming a type that is used as an embedded field
|
||||
// requires renaming the field too. e.g.
|
||||
// type T int // if we rename this to U..
|
||||
// var s struct {T}
|
||||
// print(s.T) // ...this must change too
|
||||
if _, ok := from.(*types.TypeName); ok {
|
||||
for id, obj := range info.Uses {
|
||||
if obj == from {
|
||||
if field := info.Defs[id]; field != nil {
|
||||
r.check(field)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lexicalLookup is like (*types.Scope).LookupParent but respects the
|
||||
// environment visible at pos. It assumes the relative position
|
||||
// information is correct with each file.
|
||||
func lexicalLookup(block *types.Scope, name string, pos token.Pos) (*types.Scope, types.Object) {
|
||||
for b := block; b != nil; b = b.Parent() {
|
||||
obj := b.Lookup(name)
|
||||
// The scope of a package-level object is the entire package,
|
||||
// so ignore pos in that case.
|
||||
// No analogous clause is needed for file-level objects
|
||||
// since no reference can appear before an import decl.
|
||||
if obj != nil && (b == obj.Pkg().Scope() || obj.Pos() < pos) {
|
||||
return b, obj
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// deeper reports whether block x is lexically deeper than y.
|
||||
func deeper(x, y *types.Scope) bool {
|
||||
if x == y || x == nil {
|
||||
return false
|
||||
} else if y == nil {
|
||||
return true
|
||||
} else {
|
||||
return deeper(x.Parent(), y.Parent())
|
||||
}
|
||||
}
|
||||
|
||||
// forEachLexicalRef calls fn(id, block) for each identifier id in package
|
||||
// info that is a reference to obj in lexical scope. block is the
|
||||
// lexical block enclosing the reference. If fn returns false the
|
||||
// iteration is terminated and findLexicalRefs returns false.
|
||||
func forEachLexicalRef(info *loader.PackageInfo, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool {
|
||||
ok := true
|
||||
var stack []ast.Node
|
||||
|
||||
var visit func(n ast.Node) bool
|
||||
visit = func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
return false
|
||||
}
|
||||
if !ok {
|
||||
return false // bail out
|
||||
}
|
||||
|
||||
stack = append(stack, n) // push
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
if info.Uses[n] == obj {
|
||||
block := enclosingBlock(&info.Info, stack)
|
||||
if !fn(n, block) {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
return visit(nil) // pop stack
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// don't visit n.Sel
|
||||
ast.Inspect(n.X, visit)
|
||||
return visit(nil) // pop stack, don't descend
|
||||
|
||||
case *ast.CompositeLit:
|
||||
// Handle recursion ourselves for struct literals
|
||||
// so we don't visit field identifiers.
|
||||
tv := info.Types[n]
|
||||
if _, ok := deref(tv.Type).Underlying().(*types.Struct); ok {
|
||||
if n.Type != nil {
|
||||
ast.Inspect(n.Type, visit)
|
||||
}
|
||||
for _, elt := range n.Elts {
|
||||
if kv, ok := elt.(*ast.KeyValueExpr); ok {
|
||||
ast.Inspect(kv.Value, visit)
|
||||
} else {
|
||||
ast.Inspect(elt, visit)
|
||||
}
|
||||
}
|
||||
return visit(nil) // pop stack, don't descend
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for _, f := range info.Files {
|
||||
ast.Inspect(f, visit)
|
||||
if len(stack) != 0 {
|
||||
panic(stack)
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// enclosingBlock returns the innermost block enclosing the specified
|
||||
// AST node, specified in the form of a path from the root of the file,
|
||||
// [file...n].
|
||||
func enclosingBlock(info *types.Info, stack []ast.Node) *types.Scope {
|
||||
for i := range stack {
|
||||
n := stack[len(stack)-1-i]
|
||||
// For some reason, go/types always associates a
|
||||
// function's scope with its FuncType.
|
||||
// TODO(adonovan): feature or a bug?
|
||||
switch f := n.(type) {
|
||||
case *ast.FuncDecl:
|
||||
n = f.Type
|
||||
case *ast.FuncLit:
|
||||
n = f.Type
|
||||
}
|
||||
if b := info.Scopes[n]; b != nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
panic("no Scope for *ast.File")
|
||||
}
|
||||
|
||||
func (r *renamer) checkLabel(label *types.Label) {
|
||||
// Check there are no identical labels in the function's label block.
|
||||
// (Label blocks don't nest, so this is easy.)
|
||||
if prev := label.Parent().Lookup(r.to); prev != nil {
|
||||
r.errorf(label.Pos(), "renaming this label %q to %q", label.Name(), prev.Name())
|
||||
r.errorf(prev.Pos(), "\twould conflict with this one")
|
||||
}
|
||||
}
|
||||
|
||||
// checkStructField checks that the field renaming will not cause
|
||||
// conflicts at its declaration, or ambiguity or changes to any selection.
|
||||
func (r *renamer) checkStructField(from *types.Var) {
|
||||
// Check that the struct declaration is free of field conflicts,
|
||||
// and field/method conflicts.
|
||||
|
||||
// go/types offers no easy way to get from a field (or interface
|
||||
// method) to its declaring struct (or interface), so we must
|
||||
// ascend the AST.
|
||||
info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos())
|
||||
// path matches this pattern:
|
||||
// [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File]
|
||||
|
||||
// Ascend to FieldList.
|
||||
var i int
|
||||
for {
|
||||
if _, ok := path[i].(*ast.FieldList); ok {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
i++
|
||||
tStruct := path[i].(*ast.StructType)
|
||||
i++
|
||||
// Ascend past parens (unlikely).
|
||||
for {
|
||||
_, ok := path[i].(*ast.ParenExpr)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if spec, ok := path[i].(*ast.TypeSpec); ok {
|
||||
// This struct is also a named type.
|
||||
// We must check for direct (non-promoted) field/field
|
||||
// and method/field conflicts.
|
||||
named := info.Defs[spec.Name].Type()
|
||||
prev, indices, _ := types.LookupFieldOrMethod(named, true, info.Pkg, r.to)
|
||||
if len(indices) == 1 {
|
||||
r.errorf(from.Pos(), "renaming this field %q to %q",
|
||||
from.Name(), r.to)
|
||||
r.errorf(prev.Pos(), "\twould conflict with this %s",
|
||||
objectKind(prev))
|
||||
return // skip checkSelections to avoid redundant errors
|
||||
}
|
||||
} else {
|
||||
// This struct is not a named type.
|
||||
// We need only check for direct (non-promoted) field/field conflicts.
|
||||
T := info.Types[tStruct].Type.Underlying().(*types.Struct)
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
if prev := T.Field(i); prev.Name() == r.to {
|
||||
r.errorf(from.Pos(), "renaming this field %q to %q",
|
||||
from.Name(), r.to)
|
||||
r.errorf(prev.Pos(), "\twould conflict with this field")
|
||||
return // skip checkSelections to avoid redundant errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renaming an anonymous field requires renaming the type too. e.g.
|
||||
// print(s.T) // if we rename T to U,
|
||||
// type T int // this and
|
||||
// var s struct {T} // this must change too.
|
||||
if from.Anonymous() {
|
||||
if named, ok := from.Type().(*types.Named); ok {
|
||||
r.check(named.Obj())
|
||||
} else if named, ok := deref(from.Type()).(*types.Named); ok {
|
||||
r.check(named.Obj())
|
||||
}
|
||||
}
|
||||
|
||||
// Check integrity of existing (field and method) selections.
|
||||
r.checkSelections(from)
|
||||
}
|
||||
|
||||
// checkSelection checks that all uses and selections that resolve to
|
||||
// the specified object would continue to do so after the renaming.
|
||||
func (r *renamer) checkSelections(from types.Object) {
|
||||
for pkg, info := range r.packages {
|
||||
if id := someUse(info, from); id != nil {
|
||||
if !r.checkExport(id, pkg, from) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for syntax, sel := range info.Selections {
|
||||
// There may be extant selections of only the old
|
||||
// name or only the new name, so we must check both.
|
||||
// (If neither, the renaming is sound.)
|
||||
//
|
||||
// In both cases, we wish to compare the lengths
|
||||
// of the implicit field path (Selection.Index)
|
||||
// to see if the renaming would change it.
|
||||
//
|
||||
// If a selection that resolves to 'from', when renamed,
|
||||
// would yield a path of the same or shorter length,
|
||||
// this indicates ambiguity or a changed referent,
|
||||
// analogous to same- or sub-block lexical conflict.
|
||||
//
|
||||
// If a selection using the name 'to' would
|
||||
// yield a path of the same or shorter length,
|
||||
// this indicates ambiguity or shadowing,
|
||||
// analogous to same- or super-block lexical conflict.
|
||||
|
||||
// TODO(adonovan): fix: derive from Types[syntax.X].Mode
|
||||
// TODO(adonovan): test with pointer, value, addressable value.
|
||||
isAddressable := true
|
||||
|
||||
if sel.Obj() == from {
|
||||
if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), r.to); obj != nil {
|
||||
// Renaming this existing selection of
|
||||
// 'from' may block access to an existing
|
||||
// type member named 'to'.
|
||||
delta := len(indices) - len(sel.Index())
|
||||
if delta > 0 {
|
||||
continue // no ambiguity
|
||||
}
|
||||
r.selectionConflict(from, delta, syntax, obj)
|
||||
return
|
||||
}
|
||||
|
||||
} else if sel.Obj().Name() == r.to {
|
||||
if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), from.Name()); obj == from {
|
||||
// Renaming 'from' may cause this existing
|
||||
// selection of the name 'to' to change
|
||||
// its meaning.
|
||||
delta := len(indices) - len(sel.Index())
|
||||
if delta > 0 {
|
||||
continue // no ambiguity
|
||||
}
|
||||
r.selectionConflict(from, -delta, syntax, sel.Obj())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renamer) selectionConflict(from types.Object, delta int, syntax *ast.SelectorExpr, obj types.Object) {
|
||||
r.errorf(from.Pos(), "renaming this %s %q to %q",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
|
||||
switch {
|
||||
case delta < 0:
|
||||
// analogous to sub-block conflict
|
||||
r.errorf(syntax.Sel.Pos(),
|
||||
"\twould change the referent of this selection")
|
||||
r.errorf(obj.Pos(), "\tof this %s", objectKind(obj))
|
||||
case delta == 0:
|
||||
// analogous to same-block conflict
|
||||
r.errorf(syntax.Sel.Pos(),
|
||||
"\twould make this reference ambiguous")
|
||||
r.errorf(obj.Pos(), "\twith this %s", objectKind(obj))
|
||||
case delta > 0:
|
||||
// analogous to super-block conflict
|
||||
r.errorf(syntax.Sel.Pos(),
|
||||
"\twould shadow this selection")
|
||||
r.errorf(obj.Pos(), "\tof the %s declared here",
|
||||
objectKind(obj))
|
||||
}
|
||||
}
|
||||
|
||||
// checkMethod performs safety checks for renaming a method.
|
||||
// There are three hazards:
|
||||
// - declaration conflicts
|
||||
// - selection ambiguity/changes
|
||||
// - entailed renamings of assignable concrete/interface types.
|
||||
// We reject renamings initiated at concrete methods if it would
|
||||
// change the assignability relation. For renamings of abstract
|
||||
// methods, we rename all methods transitively coupled to it via
|
||||
// assignability.
|
||||
func (r *renamer) checkMethod(from *types.Func) {
|
||||
// e.g. error.Error
|
||||
if from.Pkg() == nil {
|
||||
r.errorf(from.Pos(), "you cannot rename built-in method %s", from)
|
||||
return
|
||||
}
|
||||
|
||||
// ASSIGNABILITY: We reject renamings of concrete methods that
|
||||
// would break a 'satisfy' constraint; but renamings of abstract
|
||||
// methods are allowed to proceed, and we rename affected
|
||||
// concrete and abstract methods as necessary. It is the
|
||||
// initial method that determines the policy.
|
||||
|
||||
// Check for conflict at point of declaration.
|
||||
// Check to ensure preservation of assignability requirements.
|
||||
R := recv(from).Type()
|
||||
if isInterface(R) {
|
||||
// Abstract method
|
||||
|
||||
// declaration
|
||||
prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), r.to)
|
||||
if prev != nil {
|
||||
r.errorf(from.Pos(), "renaming this interface method %q to %q",
|
||||
from.Name(), r.to)
|
||||
r.errorf(prev.Pos(), "\twould conflict with this method")
|
||||
return
|
||||
}
|
||||
|
||||
// Check all interfaces that embed this one for
|
||||
// declaration conflicts too.
|
||||
for _, info := range r.packages {
|
||||
// Start with named interface types (better errors)
|
||||
for _, obj := range info.Defs {
|
||||
if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) {
|
||||
f, _, _ := types.LookupFieldOrMethod(
|
||||
obj.Type(), false, from.Pkg(), from.Name())
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
t, _, _ := types.LookupFieldOrMethod(
|
||||
obj.Type(), false, from.Pkg(), r.to)
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
r.errorf(from.Pos(), "renaming this interface method %q to %q",
|
||||
from.Name(), r.to)
|
||||
r.errorf(t.Pos(), "\twould conflict with this method")
|
||||
r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Now look at all literal interface types (includes named ones again).
|
||||
for e, tv := range info.Types {
|
||||
if e, ok := e.(*ast.InterfaceType); ok {
|
||||
_ = e
|
||||
_ = tv.Type.(*types.Interface)
|
||||
// TODO(adonovan): implement same check as above.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assignability
|
||||
//
|
||||
// Find the set of concrete or abstract methods directly
|
||||
// coupled to abstract method 'from' by some
|
||||
// satisfy.Constraint, and rename them too.
|
||||
for key := range r.satisfy() {
|
||||
// key = (lhs, rhs) where lhs is always an interface.
|
||||
|
||||
lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
|
||||
if lsel == nil {
|
||||
continue
|
||||
}
|
||||
rmethods := r.msets.MethodSet(key.RHS)
|
||||
rsel := rmethods.Lookup(from.Pkg(), from.Name())
|
||||
if rsel == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If both sides have a method of this name,
|
||||
// and one of them is m, the other must be coupled.
|
||||
var coupled *types.Func
|
||||
switch from {
|
||||
case lsel.Obj():
|
||||
coupled = rsel.Obj().(*types.Func)
|
||||
case rsel.Obj():
|
||||
coupled = lsel.Obj().(*types.Func)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// We must treat concrete-to-interface
|
||||
// constraints like an implicit selection C.f of
|
||||
// each interface method I.f, and check that the
|
||||
// renaming leaves the selection unchanged and
|
||||
// unambiguous.
|
||||
//
|
||||
// Fun fact: the implicit selection of C.f
|
||||
// type I interface{f()}
|
||||
// type C struct{I}
|
||||
// func (C) g()
|
||||
// var _ I = C{} // here
|
||||
// yields abstract method I.f. This can make error
|
||||
// messages less than obvious.
|
||||
//
|
||||
if !isInterface(key.RHS) {
|
||||
// The logic below was derived from checkSelections.
|
||||
|
||||
rtosel := rmethods.Lookup(from.Pkg(), r.to)
|
||||
if rtosel != nil {
|
||||
rto := rtosel.Obj().(*types.Func)
|
||||
delta := len(rsel.Index()) - len(rtosel.Index())
|
||||
if delta < 0 {
|
||||
continue // no ambiguity
|
||||
}
|
||||
|
||||
// TODO(adonovan): record the constraint's position.
|
||||
keyPos := token.NoPos
|
||||
|
||||
r.errorf(from.Pos(), "renaming this method %q to %q",
|
||||
from.Name(), r.to)
|
||||
if delta == 0 {
|
||||
// analogous to same-block conflict
|
||||
r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous",
|
||||
r.to, key.RHS, key.LHS)
|
||||
r.errorf(rto.Pos(), "\twith (%s).%s",
|
||||
recv(rto).Type(), r.to)
|
||||
} else {
|
||||
// analogous to super-block conflict
|
||||
r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s",
|
||||
r.to, key.RHS, key.LHS)
|
||||
r.errorf(coupled.Pos(), "\tfrom (%s).%s",
|
||||
recv(coupled).Type(), r.to)
|
||||
r.errorf(rto.Pos(), "\tto (%s).%s",
|
||||
recv(rto).Type(), r.to)
|
||||
}
|
||||
return // one error is enough
|
||||
}
|
||||
}
|
||||
|
||||
if !r.changeMethods {
|
||||
// This should be unreachable.
|
||||
r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from)
|
||||
r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled)
|
||||
r.errorf(from.Pos(), "\tPlease file a bug report")
|
||||
return
|
||||
}
|
||||
|
||||
// Rename the coupled method to preserve assignability.
|
||||
r.check(coupled)
|
||||
}
|
||||
} else {
|
||||
// Concrete method
|
||||
|
||||
// declaration
|
||||
prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), r.to)
|
||||
if prev != nil && len(indices) == 1 {
|
||||
r.errorf(from.Pos(), "renaming this method %q to %q",
|
||||
from.Name(), r.to)
|
||||
r.errorf(prev.Pos(), "\twould conflict with this %s",
|
||||
objectKind(prev))
|
||||
return
|
||||
}
|
||||
|
||||
// assignability
|
||||
//
|
||||
// Find the set of abstract methods coupled to concrete
|
||||
// method 'from' by some satisfy.Constraint, and rename
|
||||
// them too.
|
||||
//
|
||||
// Coupling may be indirect, e.g. I.f <-> C.f via type D.
|
||||
//
|
||||
// type I interface {f()}
|
||||
// type C int
|
||||
// type (C) f()
|
||||
// type D struct{C}
|
||||
// var _ I = D{}
|
||||
//
|
||||
for key := range r.satisfy() {
|
||||
// key = (lhs, rhs) where lhs is always an interface.
|
||||
if isInterface(key.RHS) {
|
||||
continue
|
||||
}
|
||||
rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name())
|
||||
if rsel == nil || rsel.Obj() != from {
|
||||
continue // rhs does not have the method
|
||||
}
|
||||
lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
|
||||
if lsel == nil {
|
||||
continue
|
||||
}
|
||||
imeth := lsel.Obj().(*types.Func)
|
||||
|
||||
// imeth is the abstract method (e.g. I.f)
|
||||
// and key.RHS is the concrete coupling type (e.g. D).
|
||||
if !r.changeMethods {
|
||||
r.errorf(from.Pos(), "renaming this method %q to %q",
|
||||
from.Name(), r.to)
|
||||
var pos token.Pos
|
||||
var iface string
|
||||
|
||||
I := recv(imeth).Type()
|
||||
if named, ok := I.(*types.Named); ok {
|
||||
pos = named.Obj().Pos()
|
||||
iface = "interface " + named.Obj().Name()
|
||||
} else {
|
||||
pos = from.Pos()
|
||||
iface = I.String()
|
||||
}
|
||||
r.errorf(pos, "\twould make %s no longer assignable to %s",
|
||||
key.RHS, iface)
|
||||
r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)",
|
||||
I, from.Name())
|
||||
return // one error is enough
|
||||
}
|
||||
|
||||
// Rename the coupled interface method to preserve assignability.
|
||||
r.check(imeth)
|
||||
}
|
||||
}
|
||||
|
||||
// Check integrity of existing (field and method) selections.
|
||||
// We skip this if there were errors above, to avoid redundant errors.
|
||||
r.checkSelections(from)
|
||||
}
|
||||
|
||||
func (r *renamer) checkExport(id *ast.Ident, pkg *types.Package, from types.Object) bool {
|
||||
// Reject cross-package references if r.to is unexported.
|
||||
// (Such references may be qualified identifiers or field/method
|
||||
// selections.)
|
||||
if !ast.IsExported(r.to) && pkg != from.Pkg() {
|
||||
r.errorf(from.Pos(),
|
||||
"renaming this %s %q to %q would make it unexported",
|
||||
objectKind(from), from.Name(), r.to)
|
||||
r.errorf(id.Pos(), "\tbreaking references from packages such as %q",
|
||||
pkg.Path())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// satisfy returns the set of interface satisfaction constraints.
|
||||
func (r *renamer) satisfy() map[satisfy.Constraint]bool {
|
||||
if r.satisfyConstraints == nil {
|
||||
// Compute on demand: it's expensive.
|
||||
var f satisfy.Finder
|
||||
for _, info := range r.packages {
|
||||
f.Find(&info.Info, info.Files)
|
||||
}
|
||||
r.satisfyConstraints = f.Result
|
||||
}
|
||||
return r.satisfyConstraints
|
||||
}
|
||||
|
||||
// -- helpers ----------------------------------------------------------
|
||||
|
||||
// recv returns the method's receiver.
|
||||
func recv(meth *types.Func) *types.Var {
|
||||
return meth.Type().(*types.Signature).Recv()
|
||||
}
|
||||
|
||||
// someUse returns an arbitrary use of obj within info.
|
||||
func someUse(info *loader.PackageInfo, obj types.Object) *ast.Ident {
|
||||
for id, o := range info.Uses {
|
||||
if o == obj {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// -- Plundered from golang.org/x/tools/go/ssa -----------------
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, _ := typ.(*types.Pointer); p != nil {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
375
vendor/golang.org/x/tools/refactor/rename/mvpkg.go
generated
vendored
Normal file
375
vendor/golang.org/x/tools/refactor/rename/mvpkg.go
generated
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// licence that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the implementation of the 'gomvpkg' command
|
||||
// whose main function is in golang.org/x/tools/cmd/gomvpkg.
|
||||
|
||||
package rename
|
||||
|
||||
// TODO(matloob):
|
||||
// - think about what happens if the package is moving across version control systems.
|
||||
// - think about windows, which uses "\" as its directory separator.
|
||||
// - dot imports are not supported. Make sure it's clearly documented.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
)
|
||||
|
||||
// Move, given a package path and a destination package path, will try
|
||||
// to move the given package to the new path. The Move function will
|
||||
// first check for any conflicts preventing the move, such as a
|
||||
// package already existing at the destination package path. If the
|
||||
// move can proceed, it builds an import graph to find all imports of
|
||||
// the packages whose paths need to be renamed. This includes uses of
|
||||
// the subpackages of the package to be moved as those packages will
|
||||
// also need to be moved. It then renames all imports to point to the
|
||||
// new paths, and then moves the packages to their new paths.
|
||||
func Move(ctxt *build.Context, from, to, moveTmpl string) error {
|
||||
srcDir, err := srcDir(ctxt, from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This should be the only place in the program that constructs
|
||||
// file paths.
|
||||
// TODO(matloob): test on Microsoft Windows.
|
||||
fromDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(from))
|
||||
toDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(to))
|
||||
toParent := filepath.Dir(toDir)
|
||||
if !buildutil.IsDir(ctxt, toParent) {
|
||||
return fmt.Errorf("parent directory does not exist for path %s", toDir)
|
||||
}
|
||||
|
||||
// Build the import graph and figure out which packages to update.
|
||||
_, rev, errors := importgraph.Build(ctxt)
|
||||
if len(errors) > 0 {
|
||||
// With a large GOPATH tree, errors are inevitable.
|
||||
// Report them but proceed.
|
||||
fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n")
|
||||
for path, err := range errors {
|
||||
fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the affected packages---the set of packages whose import
|
||||
// statements need updating.
|
||||
affectedPackages := map[string]bool{from: true}
|
||||
destinations := make(map[string]string) // maps old import path to new import path
|
||||
for pkg := range subpackages(ctxt, srcDir, from) {
|
||||
for r := range rev[pkg] {
|
||||
affectedPackages[r] = true
|
||||
}
|
||||
// Ensure directories have a trailing separator.
|
||||
dest := strings.Replace(pkg,
|
||||
filepath.Join(from, ""),
|
||||
filepath.Join(to, ""),
|
||||
1)
|
||||
destinations[pkg] = filepath.ToSlash(dest)
|
||||
}
|
||||
|
||||
// Load all the affected packages.
|
||||
iprog, err := loadProgram(ctxt, affectedPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare the move command, if one was supplied.
|
||||
var cmd string
|
||||
if moveTmpl != "" {
|
||||
if cmd, err = moveCmd(moveTmpl, fromDir, toDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m := mover{
|
||||
ctxt: ctxt,
|
||||
rev: rev,
|
||||
iprog: iprog,
|
||||
from: from,
|
||||
to: to,
|
||||
fromDir: fromDir,
|
||||
toDir: toDir,
|
||||
affectedPackages: affectedPackages,
|
||||
destinations: destinations,
|
||||
cmd: cmd,
|
||||
}
|
||||
|
||||
if err := m.checkValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.move()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// srcDir returns the absolute path of the srcdir containing pkg.
|
||||
func srcDir(ctxt *build.Context, pkg string) (string, error) {
|
||||
for _, srcDir := range ctxt.SrcDirs() {
|
||||
path := buildutil.JoinPath(ctxt, srcDir, pkg)
|
||||
if buildutil.IsDir(ctxt, path) {
|
||||
return srcDir, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("src dir not found for package: %s", pkg)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Only process the package or a sub-package
|
||||
if !(strings.HasPrefix(pkg, dir) &&
|
||||
(len(pkg) == len(dir) || pkg[len(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
|
||||
}
|
||||
|
||||
type mover struct {
|
||||
// iprog contains all packages whose contents need to be updated
|
||||
// with new package names or import paths.
|
||||
iprog *loader.Program
|
||||
ctxt *build.Context
|
||||
// rev is the reverse import graph.
|
||||
rev importgraph.Graph
|
||||
// from and to are the source and destination import
|
||||
// paths. fromDir and toDir are the source and destination
|
||||
// absolute paths that package source files will be moved between.
|
||||
from, to, fromDir, toDir string
|
||||
// affectedPackages is the set of all packages whose contents need
|
||||
// to be updated to reflect new package names or import paths.
|
||||
affectedPackages map[string]bool
|
||||
// destinations maps each subpackage to be moved to its
|
||||
// destination path.
|
||||
destinations map[string]string
|
||||
// cmd, if not empty, will be executed to move fromDir to toDir.
|
||||
cmd string
|
||||
}
|
||||
|
||||
func (m *mover) checkValid() error {
|
||||
const prefix = "invalid move destination"
|
||||
|
||||
match, err := regexp.MatchString("^[_\\pL][_\\pL\\p{Nd}]*$", path.Base(m.to))
|
||||
if err != nil {
|
||||
panic("regexp.MatchString failed")
|
||||
}
|
||||
if !match {
|
||||
return fmt.Errorf("%s: %s; gomvpkg does not support move destinations "+
|
||||
"whose base names are not valid go identifiers", prefix, m.to)
|
||||
}
|
||||
|
||||
if buildutil.FileExists(m.ctxt, m.toDir) {
|
||||
return fmt.Errorf("%s: %s conflicts with file %s", prefix, m.to, m.toDir)
|
||||
}
|
||||
if buildutil.IsDir(m.ctxt, m.toDir) {
|
||||
return fmt.Errorf("%s: %s conflicts with directory %s", prefix, m.to, m.toDir)
|
||||
}
|
||||
|
||||
for _, toSubPkg := range m.destinations {
|
||||
if _, err := m.ctxt.Import(toSubPkg, "", build.FindOnly); err == nil {
|
||||
return fmt.Errorf("%s: %s; package or subpackage %s already exists",
|
||||
prefix, m.to, toSubPkg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// moveCmd produces the version control move command used to move fromDir to toDir by
|
||||
// executing the given template.
|
||||
func moveCmd(moveTmpl, fromDir, toDir string) (string, error) {
|
||||
tmpl, err := template.New("movecmd").Parse(moveTmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, struct {
|
||||
Src string
|
||||
Dst string
|
||||
}{fromDir, toDir})
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
func (m *mover) move() error {
|
||||
filesToUpdate := make(map[*ast.File]bool)
|
||||
|
||||
// Change the moved package's "package" declaration to its new base name.
|
||||
pkg, ok := m.iprog.Imported[m.from]
|
||||
if !ok {
|
||||
log.Fatalf("unexpected: package %s is not in import map", m.from)
|
||||
}
|
||||
newName := filepath.Base(m.to)
|
||||
for _, f := range pkg.Files {
|
||||
// Update all import comments.
|
||||
for _, cg := range f.Comments {
|
||||
c := cg.List[0]
|
||||
if c.Slash >= f.Name.End() &&
|
||||
sameLine(m.iprog.Fset, c.Slash, f.Name.End()) &&
|
||||
(f.Decls == nil || c.Slash < f.Decls[0].Pos()) {
|
||||
if strings.HasPrefix(c.Text, `// import "`) {
|
||||
c.Text = `// import "` + m.to + `"`
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(c.Text, `/* import "`) {
|
||||
c.Text = `/* import "` + m.to + `" */`
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
f.Name.Name = newName // change package decl
|
||||
filesToUpdate[f] = true
|
||||
}
|
||||
|
||||
// Look through the external test packages (m.iprog.Created contains the external test packages).
|
||||
for _, info := range m.iprog.Created {
|
||||
// Change the "package" declaration of the external test package.
|
||||
if info.Pkg.Path() == m.from+"_test" {
|
||||
for _, f := range info.Files {
|
||||
f.Name.Name = newName + "_test" // change package decl
|
||||
filesToUpdate[f] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all the loaded external test packages, which import the "from" package,
|
||||
// as affected packages and update the imports.
|
||||
for _, imp := range info.Pkg.Imports() {
|
||||
if imp.Path() == m.from {
|
||||
m.affectedPackages[info.Pkg.Path()] = true
|
||||
m.iprog.Imported[info.Pkg.Path()] = info
|
||||
if err := importName(m.iprog, info, m.from, path.Base(m.from), newName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update imports of that package to use the new import name.
|
||||
// None of the subpackages will change their name---only the from package
|
||||
// itself will.
|
||||
for p := range m.rev[m.from] {
|
||||
if err := importName(m.iprog, m.iprog.Imported[p], m.from, path.Base(m.from), newName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update import paths for all imports by affected packages.
|
||||
for ap := range m.affectedPackages {
|
||||
info, ok := m.iprog.Imported[ap]
|
||||
if !ok {
|
||||
log.Fatalf("unexpected: package %s is not in import map", ap)
|
||||
}
|
||||
for _, f := range info.Files {
|
||||
for _, imp := range f.Imports {
|
||||
importPath, _ := strconv.Unquote(imp.Path.Value)
|
||||
if newPath, ok := m.destinations[importPath]; ok {
|
||||
imp.Path.Value = strconv.Quote(newPath)
|
||||
|
||||
oldName := path.Base(importPath)
|
||||
if imp.Name != nil {
|
||||
oldName = imp.Name.Name
|
||||
}
|
||||
|
||||
newName := path.Base(newPath)
|
||||
if imp.Name == nil && oldName != newName {
|
||||
imp.Name = ast.NewIdent(oldName)
|
||||
} else if imp.Name == nil || imp.Name.Name == newName {
|
||||
imp.Name = nil
|
||||
}
|
||||
filesToUpdate[f] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for f := range filesToUpdate {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, m.iprog.Fset, f); err != nil {
|
||||
log.Printf("failed to pretty-print syntax tree: %v", err)
|
||||
continue
|
||||
}
|
||||
tokenFile := m.iprog.Fset.File(f.Pos())
|
||||
writeFile(tokenFile.Name(), buf.Bytes())
|
||||
}
|
||||
|
||||
// Move the directories.
|
||||
// If either the fromDir or toDir are contained under version control it is
|
||||
// the user's responsibility to provide a custom move command that updates
|
||||
// version control to reflect the move.
|
||||
// TODO(matloob): If the parent directory of toDir does not exist, create it.
|
||||
// For now, it's required that it does exist.
|
||||
|
||||
if m.cmd != "" {
|
||||
// TODO(matloob): Verify that the windows and plan9 cases are correct.
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", m.cmd)
|
||||
case "plan9":
|
||||
cmd = exec.Command("rc", "-c", m.cmd)
|
||||
default:
|
||||
cmd = exec.Command("sh", "-c", m.cmd)
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("version control system's move command failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return moveDirectory(m.fromDir, m.toDir)
|
||||
}
|
||||
|
||||
// sameLine reports whether two positions in the same file are on the same line.
|
||||
func sameLine(fset *token.FileSet, x, y token.Pos) bool {
|
||||
return fset.Position(x).Line == fset.Position(y).Line
|
||||
}
|
||||
|
||||
var moveDirectory = func(from, to string) error {
|
||||
return os.Rename(from, to)
|
||||
}
|
463
vendor/golang.org/x/tools/refactor/rename/mvpkg_test.go
generated
vendored
Normal file
463
vendor/golang.org/x/tools/refactor/rename/mvpkg_test.go
generated
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// licence that can be found in the LICENSE file.
|
||||
|
||||
package rename
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
ctxt *build.Context
|
||||
from, to string
|
||||
want string // regexp to match error, or "OK"
|
||||
}{
|
||||
// Simple example.
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"foo": {`package foo; type T int`},
|
||||
"bar": {`package bar`},
|
||||
"main": {`package main
|
||||
|
||||
import "foo"
|
||||
|
||||
var _ foo.T
|
||||
`},
|
||||
}),
|
||||
from: "foo", to: "bar",
|
||||
want: `invalid move destination: bar conflicts with directory .go.src.bar`,
|
||||
},
|
||||
// Subpackage already exists.
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"foo": {`package foo; type T int`},
|
||||
"foo/sub": {`package sub`},
|
||||
"bar/sub": {`package sub`},
|
||||
"main": {`package main
|
||||
|
||||
import "foo"
|
||||
|
||||
var _ foo.T
|
||||
`},
|
||||
}),
|
||||
from: "foo", to: "bar",
|
||||
want: "invalid move destination: bar; package or subpackage bar/sub already exists",
|
||||
},
|
||||
// Invalid base name.
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"foo": {`package foo; type T int`},
|
||||
"main": {`package main
|
||||
|
||||
import "foo"
|
||||
|
||||
var _ foo.T
|
||||
`},
|
||||
}),
|
||||
from: "foo", to: "bar-v2.0",
|
||||
want: "invalid move destination: bar-v2.0; gomvpkg does not " +
|
||||
"support move destinations whose base names are not valid " +
|
||||
"go identifiers",
|
||||
},
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"foo": {``},
|
||||
"bar": {`package bar`},
|
||||
}),
|
||||
from: "foo", to: "bar",
|
||||
want: `no initial packages were loaded`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ctxt := test.ctxt
|
||||
|
||||
got := make(map[string]string)
|
||||
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: nil error. Expected error: %s", prefix, test.want)
|
||||
continue
|
||||
}
|
||||
matched, err2 := regexp.MatchString(test.want, err.Error())
|
||||
if err2 != nil {
|
||||
t.Errorf("regexp.MatchString failed %s", err2)
|
||||
continue
|
||||
}
|
||||
if !matched {
|
||||
t.Errorf("%s: conflict does not match expectation:\n"+
|
||||
"Error: %q\n"+
|
||||
"Pattern: %q",
|
||||
prefix, err.Error(), test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
||||
// References into subpackages where directories have overlapped names
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"foo": {},
|
||||
"foo/a": {`package a`},
|
||||
"foo/aa": {`package bar`},
|
||||
"foo/c": {`package c; import _ "foo/bar";`},
|
||||
}),
|
||||
from: "foo/a", to: "foo/spam",
|
||||
want: map[string]string{
|
||||
"/go/src/foo/spam/0.go": `package spam
|
||||
`,
|
||||
"/go/src/foo/aa/0.go": `package bar`,
|
||||
"/go/src/foo/c/0.go": `package c; import _ "foo/bar";`,
|
||||
},
|
||||
},
|
||||
|
||||
// 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) &&
|
||||
(len(path) == len(from) || path[len(from)] == filepath.Separator)) {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
603
vendor/golang.org/x/tools/refactor/rename/rename.go
generated
vendored
Normal file
603
vendor/golang.org/x/tools/refactor/rename/rename.go
generated
vendored
Normal file
@@ -0,0 +1,603 @@
|
||||
// Copyright 2014 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 rename contains the implementation of the 'gorename' command
|
||||
// whose main function is in golang.org/x/tools/cmd/gorename.
|
||||
// See the Usage constant for the command documentation.
|
||||
package rename // import "golang.org/x/tools/refactor/rename"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
"golang.org/x/tools/refactor/satisfy"
|
||||
)
|
||||
|
||||
const Usage = `gorename: precise type-safe renaming of identifiers in Go source code.
|
||||
|
||||
Usage:
|
||||
|
||||
gorename (-from <spec> | -offset <file>:#<byte-offset>) -to <name> [-force]
|
||||
|
||||
You must specify the object (named entity) to rename using the -offset
|
||||
or -from flag. Exactly one must be specified.
|
||||
|
||||
Flags:
|
||||
|
||||
-offset specifies the filename and byte offset of an identifier to rename.
|
||||
This form is intended for use by text editors.
|
||||
|
||||
-from specifies the object to rename using a query notation;
|
||||
This form is intended for interactive use at the command line.
|
||||
A legal -from query has one of the following forms:
|
||||
|
||||
"encoding/json".Decoder.Decode method of package-level named type
|
||||
(*"encoding/json".Decoder).Decode ditto, alternative syntax
|
||||
"encoding/json".Decoder.buf field of package-level named struct type
|
||||
"encoding/json".HTMLEscape package member (const, func, var, type)
|
||||
"encoding/json".Decoder.Decode::x local object x within a method
|
||||
"encoding/json".HTMLEscape::x local object x within a function
|
||||
"encoding/json"::x object x anywhere within a package
|
||||
json.go::x object x within file json.go
|
||||
|
||||
Double-quotes must be escaped when writing a shell command.
|
||||
Quotes may be omitted for single-segment import paths such as "fmt".
|
||||
|
||||
For methods, the parens and '*' on the receiver type are both
|
||||
optional.
|
||||
|
||||
It is an error if one of the ::x queries matches multiple
|
||||
objects.
|
||||
|
||||
-to the new name.
|
||||
|
||||
-force causes the renaming to proceed even if conflicts were reported.
|
||||
The resulting program may be ill-formed, or experience a change
|
||||
in behaviour.
|
||||
|
||||
WARNING: this flag may even cause the renaming tool to crash.
|
||||
(In due course this bug will be fixed by moving certain
|
||||
analyses into the type-checker.)
|
||||
|
||||
-d display diffs instead of rewriting files
|
||||
|
||||
-v enables verbose logging.
|
||||
|
||||
gorename automatically computes the set of packages that might be
|
||||
affected. For a local renaming, this is just the package specified by
|
||||
-from or -offset, but for a potentially exported name, gorename scans
|
||||
the workspace ($GOROOT and $GOPATH).
|
||||
|
||||
gorename rejects renamings of concrete methods that would change the
|
||||
assignability relation between types and interfaces. If the interface
|
||||
change was intentional, initiate the renaming at the interface method.
|
||||
|
||||
gorename rejects any renaming that would create a conflict at the point
|
||||
of declaration, or a reference conflict (ambiguity or shadowing), or
|
||||
anything else that could cause the resulting program not to compile.
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
$ gorename -offset file.go:#123 -to foo
|
||||
|
||||
Rename the object whose identifier is at byte offset 123 within file file.go.
|
||||
|
||||
$ gorename -from '"bytes".Buffer.Len' -to Size
|
||||
|
||||
Rename the "Len" method of the *bytes.Buffer type to "Size".
|
||||
|
||||
---- TODO ----
|
||||
|
||||
Correctness:
|
||||
- handle dot imports correctly
|
||||
- document limitations (reflection, 'implements' algorithm).
|
||||
- sketch a proof of exhaustiveness.
|
||||
|
||||
Features:
|
||||
- support running on packages specified as *.go files on the command line
|
||||
- support running on programs containing errors (loader.Config.AllowErrors)
|
||||
- allow users to specify a scope other than "global" (to avoid being
|
||||
stuck by neglected packages in $GOPATH that don't build).
|
||||
- support renaming the package clause (no object)
|
||||
- support renaming an import path (no ident or object)
|
||||
(requires filesystem + SCM updates).
|
||||
- detect and reject edits to autogenerated files (cgo, protobufs)
|
||||
and optionally $GOROOT packages.
|
||||
- report all conflicts, or at least all qualitatively distinct ones.
|
||||
Sometimes we stop to avoid redundancy, but
|
||||
it may give a disproportionate sense of safety in -force mode.
|
||||
- support renaming all instances of a pattern, e.g.
|
||||
all receiver vars of a given type,
|
||||
all local variables of a given type,
|
||||
all PkgNames for a given package.
|
||||
- emit JSON output for other editors and tools.
|
||||
`
|
||||
|
||||
var (
|
||||
// Force enables patching of the source files even if conflicts were reported.
|
||||
// The resulting program may be ill-formed.
|
||||
// It may even cause gorename to crash. TODO(adonovan): fix that.
|
||||
Force bool
|
||||
|
||||
// Diff causes the tool to display diffs instead of rewriting files.
|
||||
Diff bool
|
||||
|
||||
// DiffCmd specifies the diff command used by the -d feature.
|
||||
// (The command must accept a -u flag and two filename arguments.)
|
||||
DiffCmd = "diff"
|
||||
|
||||
// ConflictError is returned by Main when it aborts the renaming due to conflicts.
|
||||
// (It is distinguished because the interesting errors are the conflicts themselves.)
|
||||
ConflictError = errors.New("renaming aborted due to conflicts")
|
||||
|
||||
// Verbose enables extra logging.
|
||||
Verbose bool
|
||||
)
|
||||
|
||||
var stdout io.Writer = os.Stdout
|
||||
|
||||
type renamer struct {
|
||||
iprog *loader.Program
|
||||
objsToUpdate map[types.Object]bool
|
||||
hadConflicts bool
|
||||
from, to string
|
||||
satisfyConstraints map[satisfy.Constraint]bool
|
||||
packages map[*types.Package]*loader.PackageInfo // subset of iprog.AllPackages to inspect
|
||||
msets typeutil.MethodSetCache
|
||||
changeMethods bool
|
||||
}
|
||||
|
||||
var reportError = func(posn token.Position, message string) {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", posn, message)
|
||||
}
|
||||
|
||||
// importName renames imports of fromPath within the package specified by info.
|
||||
// If fromName is not empty, importName renames only imports as fromName.
|
||||
// If the renaming would lead to a conflict, the file is left unchanged.
|
||||
func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromName, to string) error {
|
||||
if fromName == to {
|
||||
return nil // no-op (e.g. rename x/foo to y/foo)
|
||||
}
|
||||
for _, f := range info.Files {
|
||||
var from types.Object
|
||||
for _, imp := range f.Imports {
|
||||
importPath, _ := strconv.Unquote(imp.Path.Value)
|
||||
importName := path.Base(importPath)
|
||||
if imp.Name != nil {
|
||||
importName = imp.Name.Name
|
||||
}
|
||||
if importPath == fromPath && (fromName == "" || importName == fromName) {
|
||||
from = info.Implicits[imp]
|
||||
break
|
||||
}
|
||||
}
|
||||
if from == nil {
|
||||
continue
|
||||
}
|
||||
r := renamer{
|
||||
iprog: iprog,
|
||||
objsToUpdate: make(map[types.Object]bool),
|
||||
to: to,
|
||||
packages: map[*types.Package]*loader.PackageInfo{info.Pkg: info},
|
||||
}
|
||||
r.check(from)
|
||||
if r.hadConflicts {
|
||||
reportError(iprog.Fset.Position(f.Imports[0].Pos()),
|
||||
"skipping update of this file")
|
||||
continue // ignore errors; leave the existing name
|
||||
}
|
||||
if err := r.update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error {
|
||||
// -- Parse the -from or -offset specifier ----------------------------
|
||||
|
||||
if (offsetFlag == "") == (fromFlag == "") {
|
||||
return fmt.Errorf("exactly one of the -from and -offset flags must be specified")
|
||||
}
|
||||
|
||||
if !isValidIdentifier(to) {
|
||||
return fmt.Errorf("-to %q: not a valid identifier", to)
|
||||
}
|
||||
|
||||
if Diff {
|
||||
defer func(saved func(string, []byte) error) { writeFile = saved }(writeFile)
|
||||
writeFile = diff
|
||||
}
|
||||
|
||||
var spec *spec
|
||||
var err error
|
||||
if fromFlag != "" {
|
||||
spec, err = parseFromFlag(ctxt, fromFlag)
|
||||
} else {
|
||||
spec, err = parseOffsetFlag(ctxt, offsetFlag)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if spec.fromName == to {
|
||||
return fmt.Errorf("the old and new names are the same: %s", to)
|
||||
}
|
||||
|
||||
// -- Load the program consisting of the initial package -------------
|
||||
|
||||
iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromObjects, err := findFromObjects(iprog, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// -- Load a larger program, for global renamings ---------------------
|
||||
|
||||
if requiresGlobalRename(fromObjects, to) {
|
||||
// For a local refactoring, we needn't load more
|
||||
// packages, but if the renaming affects the package's
|
||||
// API, we we must load all packages that depend on the
|
||||
// package defining the object, plus their tests.
|
||||
|
||||
if Verbose {
|
||||
log.Print("Potentially global renaming; scanning workspace...")
|
||||
}
|
||||
|
||||
// Scan the workspace and build the import graph.
|
||||
_, rev, errors := importgraph.Build(ctxt)
|
||||
if len(errors) > 0 {
|
||||
// With a large GOPATH tree, errors are inevitable.
|
||||
// Report them but proceed.
|
||||
fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n")
|
||||
for path, err := range errors {
|
||||
fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate the set of potentially affected packages.
|
||||
affectedPackages := make(map[string]bool)
|
||||
for _, obj := range fromObjects {
|
||||
// External test packages are never imported,
|
||||
// so they will never appear in the graph.
|
||||
for path := range rev.Search(obj.Pkg().Path()) {
|
||||
affectedPackages[path] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): allow the user to specify the scope,
|
||||
// or -ignore patterns? Computing the scope when we
|
||||
// don't (yet) support inputs containing errors can make
|
||||
// the tool rather brittle.
|
||||
|
||||
// Re-load the larger program.
|
||||
iprog, err = loadProgram(ctxt, affectedPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromObjects, err = findFromObjects(iprog, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// -- Do the renaming -------------------------------------------------
|
||||
|
||||
r := renamer{
|
||||
iprog: iprog,
|
||||
objsToUpdate: make(map[types.Object]bool),
|
||||
from: spec.fromName,
|
||||
to: to,
|
||||
packages: make(map[*types.Package]*loader.PackageInfo),
|
||||
}
|
||||
|
||||
// A renaming initiated at an interface method indicates the
|
||||
// intention to rename abstract and concrete methods as needed
|
||||
// to preserve assignability.
|
||||
for _, obj := range fromObjects {
|
||||
if obj, ok := obj.(*types.Func); ok {
|
||||
recv := obj.Type().(*types.Signature).Recv()
|
||||
if recv != nil && isInterface(recv.Type().Underlying()) {
|
||||
r.changeMethods = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only the initially imported packages (iprog.Imported) and
|
||||
// their external tests (iprog.Created) should be inspected or
|
||||
// modified, as only they have type-checked functions bodies.
|
||||
// The rest are just dependencies, needed only for package-level
|
||||
// type information.
|
||||
for _, info := range iprog.Imported {
|
||||
r.packages[info.Pkg] = info
|
||||
}
|
||||
for _, info := range iprog.Created { // (tests)
|
||||
r.packages[info.Pkg] = info
|
||||
}
|
||||
|
||||
for _, from := range fromObjects {
|
||||
r.check(from)
|
||||
}
|
||||
if r.hadConflicts && !Force {
|
||||
return ConflictError
|
||||
}
|
||||
return r.update()
|
||||
}
|
||||
|
||||
// loadProgram loads the specified set of packages (plus their tests)
|
||||
// and all their dependencies, from source, through the specified build
|
||||
// context. Only packages in pkgs will have their functions bodies typechecked.
|
||||
func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, error) {
|
||||
conf := loader.Config{
|
||||
Build: ctxt,
|
||||
ParserMode: parser.ParseComments,
|
||||
|
||||
// TODO(adonovan): enable this. Requires making a lot of code more robust!
|
||||
AllowErrors: false,
|
||||
}
|
||||
// Optimization: don't type-check the bodies of functions in our
|
||||
// dependencies, since we only need exported package members.
|
||||
conf.TypeCheckFuncBodies = func(p string) bool {
|
||||
return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")]
|
||||
}
|
||||
|
||||
if Verbose {
|
||||
var list []string
|
||||
for pkg := range pkgs {
|
||||
list = append(list, pkg)
|
||||
}
|
||||
sort.Strings(list)
|
||||
for _, pkg := range list {
|
||||
log.Printf("Loading package: %s", pkg)
|
||||
}
|
||||
}
|
||||
|
||||
for pkg := range pkgs {
|
||||
conf.ImportWithTests(pkg)
|
||||
}
|
||||
|
||||
// Ideally we would just return conf.Load() here, but go/types
|
||||
// reports certain "soft" errors that gc does not (Go issue 14596).
|
||||
// As a workaround, we set AllowErrors=true and then duplicate
|
||||
// the loader's error checking but allow soft errors.
|
||||
// It would be nice if the loader API permitted "AllowErrors: soft".
|
||||
conf.AllowErrors = true
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var errpkgs []string
|
||||
// Report hard errors in indirectly imported packages.
|
||||
for _, info := range prog.AllPackages {
|
||||
if containsHardErrors(info.Errors) {
|
||||
errpkgs = append(errpkgs, info.Pkg.Path())
|
||||
}
|
||||
}
|
||||
if errpkgs != nil {
|
||||
var more string
|
||||
if len(errpkgs) > 3 {
|
||||
more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
|
||||
errpkgs = errpkgs[:3]
|
||||
}
|
||||
return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
|
||||
strings.Join(errpkgs, ", "), more)
|
||||
}
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
func containsHardErrors(errors []error) bool {
|
||||
for _, err := range errors {
|
||||
if err, ok := err.(types.Error); ok && err.Soft {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// requiresGlobalRename reports whether this renaming could potentially
|
||||
// affect other packages in the Go workspace.
|
||||
func requiresGlobalRename(fromObjects []types.Object, to string) bool {
|
||||
var tfm bool
|
||||
for _, from := range fromObjects {
|
||||
if from.Exported() {
|
||||
return true
|
||||
}
|
||||
switch objectKind(from) {
|
||||
case "type", "field", "method":
|
||||
tfm = true
|
||||
}
|
||||
}
|
||||
if ast.IsExported(to) && tfm {
|
||||
// A global renaming may be necessary even if we're
|
||||
// exporting a previous unexported name, since if it's
|
||||
// the name of a type, field or method, this could
|
||||
// change selections in other packages.
|
||||
// (We include "type" in this list because a type
|
||||
// used as an embedded struct field entails a field
|
||||
// renaming.)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// update updates the input files.
|
||||
func (r *renamer) update() error {
|
||||
// We use token.File, not filename, since a file may appear to
|
||||
// belong to multiple packages and be parsed more than once.
|
||||
// token.File captures this distinction; filename does not.
|
||||
|
||||
var nidents int
|
||||
var filesToUpdate = make(map[*token.File]bool)
|
||||
docRegexp := regexp.MustCompile(`\b` + r.from + `\b`)
|
||||
for _, info := range r.packages {
|
||||
// Mutate the ASTs and note the filenames.
|
||||
for id, obj := range info.Defs {
|
||||
if r.objsToUpdate[obj] {
|
||||
nidents++
|
||||
id.Name = r.to
|
||||
filesToUpdate[r.iprog.Fset.File(id.Pos())] = true
|
||||
// Perform the rename in doc comments too.
|
||||
if doc := r.docComment(id); doc != nil {
|
||||
for _, comment := range doc.List {
|
||||
comment.Text = docRegexp.ReplaceAllString(comment.Text, r.to)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id, obj := range info.Uses {
|
||||
if r.objsToUpdate[obj] {
|
||||
nidents++
|
||||
id.Name = r.to
|
||||
filesToUpdate[r.iprog.Fset.File(id.Pos())] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renaming not supported if cgo files are affected.
|
||||
var generatedFileNames []string
|
||||
for _, info := range r.packages {
|
||||
for _, f := range info.Files {
|
||||
tokenFile := r.iprog.Fset.File(f.Pos())
|
||||
if filesToUpdate[tokenFile] && generated(f, tokenFile) {
|
||||
generatedFileNames = append(generatedFileNames, tokenFile.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
if !Force && len(generatedFileNames) > 0 {
|
||||
return fmt.Errorf("refusing to modify generated file%s containing DO NOT EDIT marker: %v", plural(len(generatedFileNames)), generatedFileNames)
|
||||
}
|
||||
|
||||
// Write affected files.
|
||||
var nerrs, npkgs int
|
||||
for _, info := range r.packages {
|
||||
first := true
|
||||
for _, f := range info.Files {
|
||||
tokenFile := r.iprog.Fset.File(f.Pos())
|
||||
if filesToUpdate[tokenFile] {
|
||||
if first {
|
||||
npkgs++
|
||||
first = false
|
||||
if Verbose {
|
||||
log.Printf("Updating package %s", info.Pkg.Path())
|
||||
}
|
||||
}
|
||||
|
||||
filename := tokenFile.Name()
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, r.iprog.Fset, f); err != nil {
|
||||
log.Printf("failed to pretty-print syntax tree: %v", err)
|
||||
nerrs++
|
||||
continue
|
||||
}
|
||||
if err := writeFile(filename, buf.Bytes()); err != nil {
|
||||
log.Print(err)
|
||||
nerrs++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !Diff {
|
||||
fmt.Printf("Renamed %d occurrence%s in %d file%s in %d package%s.\n",
|
||||
nidents, plural(nidents),
|
||||
len(filesToUpdate), plural(len(filesToUpdate)),
|
||||
npkgs, plural(npkgs))
|
||||
}
|
||||
if nerrs > 0 {
|
||||
return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// docComment returns the doc for an identifier.
|
||||
func (r *renamer) docComment(id *ast.Ident) *ast.CommentGroup {
|
||||
_, nodes, _ := r.iprog.PathEnclosingInterval(id.Pos(), id.End())
|
||||
for _, node := range nodes {
|
||||
switch decl := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
return decl.Doc
|
||||
case *ast.Field:
|
||||
return decl.Doc
|
||||
case *ast.GenDecl:
|
||||
return decl.Doc
|
||||
// For {Type,Value}Spec, if the doc on the spec is absent,
|
||||
// search for the enclosing GenDecl
|
||||
case *ast.TypeSpec:
|
||||
if decl.Doc != nil {
|
||||
return decl.Doc
|
||||
}
|
||||
case *ast.ValueSpec:
|
||||
if decl.Doc != nil {
|
||||
return decl.Doc
|
||||
}
|
||||
case *ast.Ident:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func plural(n int) string {
|
||||
if n != 1 {
|
||||
return "s"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// writeFile is a seam for testing and for the -d flag.
|
||||
var writeFile = reallyWriteFile
|
||||
|
||||
func reallyWriteFile(filename string, content []byte) error {
|
||||
return ioutil.WriteFile(filename, content, 0644)
|
||||
}
|
||||
|
||||
func diff(filename string, content []byte) error {
|
||||
renamed := fmt.Sprintf("%s.%d.renamed", filename, os.Getpid())
|
||||
if err := ioutil.WriteFile(renamed, content, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(renamed)
|
||||
|
||||
diff, err := exec.Command(DiffCmd, "-u", filename, renamed).CombinedOutput()
|
||||
if len(diff) > 0 {
|
||||
// diff exits with a non-zero status when the files don't match.
|
||||
// Ignore that failure as long as we get output.
|
||||
stdout.Write(diff)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("computing diff: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
1334
vendor/golang.org/x/tools/refactor/rename/rename_test.go
generated
vendored
Normal file
1334
vendor/golang.org/x/tools/refactor/rename/rename_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
593
vendor/golang.org/x/tools/refactor/rename/spec.go
generated
vendored
Normal file
593
vendor/golang.org/x/tools/refactor/rename/spec.go
generated
vendored
Normal file
@@ -0,0 +1,593 @@
|
||||
// Copyright 2014 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 rename
|
||||
|
||||
// This file contains logic related to specifying a renaming: parsing of
|
||||
// the flags as a form of query, and finding the object(s) it denotes.
|
||||
// See Usage for flag details.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
// A spec specifies an entity to rename.
|
||||
//
|
||||
// It is populated from an -offset flag or -from query;
|
||||
// see Usage for the allowed -from query forms.
|
||||
//
|
||||
type spec struct {
|
||||
// pkg is the package containing the position
|
||||
// specified by the -from or -offset flag.
|
||||
// If filename == "", our search for the 'from' entity
|
||||
// is restricted to this package.
|
||||
pkg string
|
||||
|
||||
// The original name of the entity being renamed.
|
||||
// If the query had a ::from component, this is that;
|
||||
// otherwise it's the last segment, e.g.
|
||||
// (encoding/json.Decoder).from
|
||||
// encoding/json.from
|
||||
fromName string
|
||||
|
||||
// -- The remaining fields are private to this file. All are optional. --
|
||||
|
||||
// The query's ::x suffix, if any.
|
||||
searchFor string
|
||||
|
||||
// e.g. "Decoder" in "(encoding/json.Decoder).fieldOrMethod"
|
||||
// or "encoding/json.Decoder
|
||||
pkgMember string
|
||||
|
||||
// e.g. fieldOrMethod in "(encoding/json.Decoder).fieldOrMethod"
|
||||
typeMember string
|
||||
|
||||
// Restricts the query to this file.
|
||||
// Implied by -from="file.go::x" and -offset flags.
|
||||
filename string
|
||||
|
||||
// Byte offset of the 'from' identifier within the file named 'filename'.
|
||||
// -offset mode only.
|
||||
offset int
|
||||
}
|
||||
|
||||
// parseFromFlag interprets the "-from" flag value as a renaming specification.
|
||||
// See Usage in rename.go for valid formats.
|
||||
func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) {
|
||||
var spec spec
|
||||
var main string // sans "::x" suffix
|
||||
switch parts := strings.Split(fromFlag, "::"); len(parts) {
|
||||
case 1:
|
||||
main = parts[0]
|
||||
case 2:
|
||||
main = parts[0]
|
||||
spec.searchFor = parts[1]
|
||||
if parts[1] == "" {
|
||||
// error
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(main, ".go") {
|
||||
// main is "filename.go"
|
||||
if spec.searchFor == "" {
|
||||
return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main)
|
||||
}
|
||||
spec.filename = main
|
||||
if !buildutil.FileExists(ctxt, spec.filename) {
|
||||
return nil, fmt.Errorf("no such file: %s", spec.filename)
|
||||
}
|
||||
|
||||
bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec.pkg = bp.ImportPath
|
||||
|
||||
} else {
|
||||
// main is one of:
|
||||
// "importpath"
|
||||
// "importpath".member
|
||||
// (*"importpath".type).fieldormethod (parens and star optional)
|
||||
if err := parseObjectSpec(&spec, main); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if spec.searchFor != "" {
|
||||
spec.fromName = spec.searchFor
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sanitize the package.
|
||||
bp, err := ctxt.Import(spec.pkg, cwd, build.FindOnly)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find package %q", spec.pkg)
|
||||
}
|
||||
spec.pkg = bp.ImportPath
|
||||
|
||||
if !isValidIdentifier(spec.fromName) {
|
||||
return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName)
|
||||
}
|
||||
|
||||
if Verbose {
|
||||
log.Printf("-from spec: %+v", spec)
|
||||
}
|
||||
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
// parseObjectSpec parses main as one of the non-filename forms of
|
||||
// object specification.
|
||||
func parseObjectSpec(spec *spec, main string) error {
|
||||
// Parse main as a Go expression, albeit a strange one.
|
||||
e, _ := parser.ParseExpr(main)
|
||||
|
||||
if pkg := parseImportPath(e); pkg != "" {
|
||||
// e.g. bytes or "encoding/json": a package
|
||||
spec.pkg = pkg
|
||||
if spec.searchFor == "" {
|
||||
return fmt.Errorf("-from %q: package import path %q must have a ::name suffix",
|
||||
main, main)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if e, ok := e.(*ast.SelectorExpr); ok {
|
||||
x := unparen(e.X)
|
||||
|
||||
// Strip off star constructor, if any.
|
||||
if star, ok := x.(*ast.StarExpr); ok {
|
||||
x = star.X
|
||||
}
|
||||
|
||||
if pkg := parseImportPath(x); pkg != "" {
|
||||
// package member e.g. "encoding/json".HTMLEscape
|
||||
spec.pkg = pkg // e.g. "encoding/json"
|
||||
spec.pkgMember = e.Sel.Name // e.g. "HTMLEscape"
|
||||
spec.fromName = e.Sel.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
if x, ok := x.(*ast.SelectorExpr); ok {
|
||||
// field/method of type e.g. ("encoding/json".Decoder).Decode
|
||||
y := unparen(x.X)
|
||||
if pkg := parseImportPath(y); pkg != "" {
|
||||
spec.pkg = pkg // e.g. "encoding/json"
|
||||
spec.pkgMember = x.Sel.Name // e.g. "Decoder"
|
||||
spec.typeMember = e.Sel.Name // e.g. "Decode"
|
||||
spec.fromName = e.Sel.Name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("-from %q: invalid expression", main)
|
||||
}
|
||||
|
||||
// parseImportPath returns the import path of the package denoted by e.
|
||||
// Any import path may be represented as a string literal;
|
||||
// single-segment import paths (e.g. "bytes") may also be represented as
|
||||
// ast.Ident. parseImportPath returns "" for all other expressions.
|
||||
func parseImportPath(e ast.Expr) string {
|
||||
switch e := e.(type) {
|
||||
case *ast.Ident:
|
||||
return e.Name // e.g. bytes
|
||||
|
||||
case *ast.BasicLit:
|
||||
if e.Kind == token.STRING {
|
||||
pkgname, _ := strconv.Unquote(e.Value)
|
||||
return pkgname // e.g. "encoding/json"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// parseOffsetFlag interprets the "-offset" flag value as a renaming specification.
|
||||
func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) {
|
||||
var spec spec
|
||||
// Validate -offset, e.g. file.go:#123
|
||||
parts := strings.Split(offsetFlag, ":#")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("-offset %q: invalid offset specification", offsetFlag)
|
||||
}
|
||||
|
||||
spec.filename = parts[0]
|
||||
if !buildutil.FileExists(ctxt, spec.filename) {
|
||||
return nil, fmt.Errorf("no such file: %s", spec.filename)
|
||||
}
|
||||
|
||||
bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec.pkg = bp.ImportPath
|
||||
|
||||
for _, r := range parts[1] {
|
||||
if !isDigit(r) {
|
||||
return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag)
|
||||
}
|
||||
}
|
||||
spec.offset, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag)
|
||||
}
|
||||
|
||||
// Parse the file and check there's an identifier at that offset.
|
||||
fset := token.NewFileSet()
|
||||
f, err := buildutil.ParseFile(fset, ctxt, nil, wd, spec.filename, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("-offset %q: cannot parse file: %s", offsetFlag, err)
|
||||
}
|
||||
|
||||
id := identAtOffset(fset, f, spec.offset)
|
||||
if id == nil {
|
||||
return nil, fmt.Errorf("-offset %q: no identifier at this position", offsetFlag)
|
||||
}
|
||||
|
||||
spec.fromName = id.Name
|
||||
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
var wd = func() string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic("cannot get working directory: " + err.Error())
|
||||
}
|
||||
return wd
|
||||
}()
|
||||
|
||||
// For source trees built with 'go build', the -from or -offset
|
||||
// spec identifies exactly one initial 'from' object to rename ,
|
||||
// but certain proprietary build systems allow a single file to
|
||||
// appear in multiple packages (e.g. the test package contains a
|
||||
// copy of its library), so there may be multiple objects for
|
||||
// the same source entity.
|
||||
|
||||
func findFromObjects(iprog *loader.Program, spec *spec) ([]types.Object, error) {
|
||||
if spec.filename != "" {
|
||||
return findFromObjectsInFile(iprog, spec)
|
||||
}
|
||||
|
||||
// Search for objects defined in specified package.
|
||||
|
||||
// TODO(adonovan): the iprog.ImportMap has an entry {"main": ...}
|
||||
// for main packages, even though that's not an import path.
|
||||
// Seems like a bug.
|
||||
//
|
||||
// pkg := iprog.ImportMap[spec.pkg]
|
||||
// if pkg == nil {
|
||||
// return fmt.Errorf("cannot find package %s", spec.pkg) // can't happen?
|
||||
// }
|
||||
// info := iprog.AllPackages[pkg]
|
||||
|
||||
// Workaround: lookup by value.
|
||||
var info *loader.PackageInfo
|
||||
var pkg *types.Package
|
||||
for pkg, info = range iprog.AllPackages {
|
||||
if pkg.Path() == spec.pkg {
|
||||
break
|
||||
}
|
||||
}
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("package %q was not loaded", spec.pkg)
|
||||
}
|
||||
|
||||
objects, err := findObjects(info, spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(objects) > 1 {
|
||||
// ambiguous "*" scope query
|
||||
return nil, ambiguityError(iprog.Fset, objects)
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) {
|
||||
var fromObjects []types.Object
|
||||
for _, info := range iprog.AllPackages {
|
||||
// restrict to specified filename
|
||||
// NB: under certain proprietary build systems, a given
|
||||
// filename may appear in multiple packages.
|
||||
for _, f := range info.Files {
|
||||
thisFile := iprog.Fset.File(f.Pos())
|
||||
if !sameFile(thisFile.Name(), spec.filename) {
|
||||
continue
|
||||
}
|
||||
// This package contains the query file.
|
||||
|
||||
if spec.offset != 0 {
|
||||
// We cannot refactor generated files since position information is invalidated.
|
||||
if generated(f, thisFile) {
|
||||
return nil, fmt.Errorf("cannot rename identifiers in generated file containing DO NOT EDIT marker: %s", thisFile.Name())
|
||||
}
|
||||
|
||||
// Search for a specific ident by file/offset.
|
||||
id := identAtOffset(iprog.Fset, f, spec.offset)
|
||||
if id == nil {
|
||||
// can't happen?
|
||||
return nil, fmt.Errorf("identifier not found")
|
||||
}
|
||||
obj := info.Uses[id]
|
||||
if obj == nil {
|
||||
obj = info.Defs[id]
|
||||
if obj == nil {
|
||||
// Ident without Object.
|
||||
|
||||
// Package clause?
|
||||
pos := thisFile.Pos(spec.offset)
|
||||
_, path, _ := iprog.PathEnclosingInterval(pos, pos)
|
||||
if len(path) == 2 { // [Ident File]
|
||||
// TODO(adonovan): support this case.
|
||||
return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported",
|
||||
path[1].(*ast.File).Name.Name)
|
||||
}
|
||||
|
||||
// Implicit y in "switch y := x.(type) {"?
|
||||
if obj := typeSwitchVar(&info.Info, path); obj != nil {
|
||||
return []types.Object{obj}, nil
|
||||
}
|
||||
|
||||
// Probably a type error.
|
||||
return nil, fmt.Errorf("cannot find object for %q", id.Name)
|
||||
}
|
||||
}
|
||||
if obj.Pkg() == nil {
|
||||
return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj)
|
||||
|
||||
}
|
||||
|
||||
fromObjects = append(fromObjects, obj)
|
||||
} else {
|
||||
// do a package-wide query
|
||||
objects, err := findObjects(info, spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// filter results: only objects defined in thisFile
|
||||
var filtered []types.Object
|
||||
for _, obj := range objects {
|
||||
if iprog.Fset.File(obj.Pos()) == thisFile {
|
||||
filtered = append(filtered, obj)
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return nil, fmt.Errorf("no object %q declared in file %s",
|
||||
spec.fromName, spec.filename)
|
||||
} else if len(filtered) > 1 {
|
||||
return nil, ambiguityError(iprog.Fset, filtered)
|
||||
}
|
||||
fromObjects = append(fromObjects, filtered[0])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(fromObjects) == 0 {
|
||||
// can't happen?
|
||||
return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename)
|
||||
}
|
||||
return fromObjects, nil
|
||||
}
|
||||
|
||||
func typeSwitchVar(info *types.Info, path []ast.Node) types.Object {
|
||||
if len(path) > 3 {
|
||||
// [Ident AssignStmt TypeSwitchStmt...]
|
||||
if sw, ok := path[2].(*ast.TypeSwitchStmt); ok {
|
||||
// choose the first case.
|
||||
if len(sw.Body.List) > 0 {
|
||||
obj := info.Implicits[sw.Body.List[0].(*ast.CaseClause)]
|
||||
if obj != nil {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// On success, findObjects returns the list of objects named
|
||||
// spec.fromName matching the spec. On success, the result has exactly
|
||||
// one element unless spec.searchFor!="", in which case it has at least one
|
||||
// element.
|
||||
//
|
||||
func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) {
|
||||
if spec.pkgMember == "" {
|
||||
if spec.searchFor == "" {
|
||||
panic(spec)
|
||||
}
|
||||
objects := searchDefs(&info.Info, spec.searchFor)
|
||||
if objects == nil {
|
||||
return nil, fmt.Errorf("no object %q declared in package %q",
|
||||
spec.searchFor, info.Pkg.Path())
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember)
|
||||
if pkgMember == nil {
|
||||
return nil, fmt.Errorf("package %q has no member %q",
|
||||
info.Pkg.Path(), spec.pkgMember)
|
||||
}
|
||||
|
||||
var searchFunc *types.Func
|
||||
if spec.typeMember == "" {
|
||||
// package member
|
||||
if spec.searchFor == "" {
|
||||
return []types.Object{pkgMember}, nil
|
||||
}
|
||||
|
||||
// Search within pkgMember, which must be a function.
|
||||
searchFunc, _ = pkgMember.(*types.Func)
|
||||
if searchFunc == nil {
|
||||
return nil, fmt.Errorf("cannot search for %q within %s %q",
|
||||
spec.searchFor, objectKind(pkgMember), pkgMember)
|
||||
}
|
||||
} else {
|
||||
// field/method of type
|
||||
// e.g. (encoding/json.Decoder).Decode
|
||||
// or ::x within it.
|
||||
|
||||
tName, _ := pkgMember.(*types.TypeName)
|
||||
if tName == nil {
|
||||
return nil, fmt.Errorf("%s.%s is a %s, not a type",
|
||||
info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember))
|
||||
}
|
||||
|
||||
// search within named type.
|
||||
obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s",
|
||||
spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name())
|
||||
}
|
||||
|
||||
if spec.searchFor == "" {
|
||||
// If it is an embedded field, return the type of the field.
|
||||
if v, ok := obj.(*types.Var); ok && v.Anonymous() {
|
||||
switch t := v.Type().(type) {
|
||||
case *types.Pointer:
|
||||
return []types.Object{t.Elem().(*types.Named).Obj()}, nil
|
||||
case *types.Named:
|
||||
return []types.Object{t.Obj()}, nil
|
||||
}
|
||||
}
|
||||
return []types.Object{obj}, nil
|
||||
}
|
||||
|
||||
searchFunc, _ = obj.(*types.Func)
|
||||
if searchFunc == nil {
|
||||
return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function",
|
||||
spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(),
|
||||
obj.Name())
|
||||
}
|
||||
if isInterface(tName.Type()) {
|
||||
return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s",
|
||||
spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// -- search within function or method --
|
||||
|
||||
decl := funcDecl(info, searchFunc)
|
||||
if decl == nil {
|
||||
return nil, fmt.Errorf("cannot find syntax for %s", searchFunc) // can't happen?
|
||||
}
|
||||
|
||||
var objects []types.Object
|
||||
for _, obj := range searchDefs(&info.Info, spec.searchFor) {
|
||||
// We use positions, not scopes, to determine whether
|
||||
// the obj is within searchFunc. This is clumsy, but the
|
||||
// alternative, using the types.Scope tree, doesn't
|
||||
// account for non-lexical objects like fields and
|
||||
// interface methods.
|
||||
if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc {
|
||||
objects = append(objects, obj)
|
||||
}
|
||||
}
|
||||
if objects == nil {
|
||||
return nil, fmt.Errorf("no local definition of %q within %s",
|
||||
spec.searchFor, searchFunc)
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func funcDecl(info *loader.PackageInfo, fn *types.Func) *ast.FuncDecl {
|
||||
for _, f := range info.Files {
|
||||
for _, d := range f.Decls {
|
||||
if d, ok := d.(*ast.FuncDecl); ok && info.Defs[d.Name] == fn {
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func searchDefs(info *types.Info, name string) []types.Object {
|
||||
var objects []types.Object
|
||||
for id, obj := range info.Defs {
|
||||
if obj == nil {
|
||||
// e.g. blank ident.
|
||||
// TODO(adonovan): but also implicit y in
|
||||
// switch y := x.(type)
|
||||
// Needs some thought.
|
||||
continue
|
||||
}
|
||||
if id.Name == name {
|
||||
objects = append(objects, obj)
|
||||
}
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
func identAtOffset(fset *token.FileSet, f *ast.File, offset int) *ast.Ident {
|
||||
var found *ast.Ident
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if id, ok := n.(*ast.Ident); ok {
|
||||
idpos := fset.Position(id.Pos()).Offset
|
||||
if idpos <= offset && offset < idpos+len(id.Name) {
|
||||
found = id
|
||||
}
|
||||
}
|
||||
return found == nil // keep traversing only until found
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
// ambiguityError returns an error describing an ambiguous "*" scope query.
|
||||
func ambiguityError(fset *token.FileSet, objects []types.Object) error {
|
||||
var buf bytes.Buffer
|
||||
for i, obj := range objects {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
posn := fset.Position(obj.Pos())
|
||||
fmt.Fprintf(&buf, "%s at %s:%d:%d",
|
||||
objectKind(obj), filepath.Base(posn.Filename), posn.Line, posn.Column)
|
||||
}
|
||||
return fmt.Errorf("ambiguous specifier %s matches %s",
|
||||
objects[0].Name(), buf.String())
|
||||
}
|
||||
|
||||
// Matches cgo generated comment as well as the proposed standard:
|
||||
// https://golang.org/s/generatedcode
|
||||
var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
|
||||
|
||||
// generated reports whether ast.File is a generated file.
|
||||
func generated(f *ast.File, tokenFile *token.File) bool {
|
||||
|
||||
// Iterate over the comments in the file
|
||||
for _, commentGroup := range f.Comments {
|
||||
for _, comment := range commentGroup.List {
|
||||
if matched := generatedRx.MatchString(comment.Text); matched {
|
||||
// Check if comment is at the beginning of the line in source
|
||||
if pos := tokenFile.Position(comment.Slash); pos.Column == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
105
vendor/golang.org/x/tools/refactor/rename/util.go
generated
vendored
Normal file
105
vendor/golang.org/x/tools/refactor/rename/util.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2014 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 rename
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func objectKind(obj types.Object) string {
|
||||
switch obj := obj.(type) {
|
||||
case *types.PkgName:
|
||||
return "imported package name"
|
||||
case *types.TypeName:
|
||||
return "type"
|
||||
case *types.Var:
|
||||
if obj.IsField() {
|
||||
return "field"
|
||||
}
|
||||
case *types.Func:
|
||||
if obj.Type().(*types.Signature).Recv() != nil {
|
||||
return "method"
|
||||
}
|
||||
}
|
||||
// label, func, var, const
|
||||
return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
|
||||
}
|
||||
|
||||
func typeKind(T types.Type) string {
|
||||
return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(T.Underlying()).String(), "*types."))
|
||||
}
|
||||
|
||||
// NB: for renamings, blank is not considered valid.
|
||||
func isValidIdentifier(id string) bool {
|
||||
if id == "" || id == "_" {
|
||||
return false
|
||||
}
|
||||
for i, r := range id {
|
||||
if !isLetter(r) && (i == 0 || !isDigit(r)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return token.Lookup(id) == token.IDENT
|
||||
}
|
||||
|
||||
// isLocal reports whether obj is local to some function.
|
||||
// Precondition: not a struct field or interface method.
|
||||
func isLocal(obj types.Object) bool {
|
||||
// [... 5=stmt 4=func 3=file 2=pkg 1=universe]
|
||||
var depth int
|
||||
for scope := obj.Parent(); scope != nil; scope = scope.Parent() {
|
||||
depth++
|
||||
}
|
||||
return depth >= 4
|
||||
}
|
||||
|
||||
func isPackageLevel(obj types.Object) bool {
|
||||
return obj.Pkg().Scope().Lookup(obj.Name()) == obj
|
||||
}
|
||||
|
||||
// -- Plundered from go/scanner: ---------------------------------------
|
||||
|
||||
func isLetter(ch rune) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
|
||||
}
|
||||
|
||||
func isDigit(ch rune) bool {
|
||||
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
|
||||
}
|
||||
|
||||
// -- Plundered from golang.org/x/tools/cmd/guru -----------------
|
||||
|
||||
// sameFile returns true if x and y have the same basename and denote
|
||||
// the same file.
|
||||
//
|
||||
func sameFile(x, y string) bool {
|
||||
if runtime.GOOS == "windows" {
|
||||
x = filepath.ToSlash(x)
|
||||
y = filepath.ToSlash(y)
|
||||
}
|
||||
if x == y {
|
||||
return true
|
||||
}
|
||||
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||
if xi, err := os.Stat(x); err == nil {
|
||||
if yi, err := os.Stat(y); err == nil {
|
||||
return os.SameFile(xi, yi)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
705
vendor/golang.org/x/tools/refactor/satisfy/find.go
generated
vendored
Normal file
705
vendor/golang.org/x/tools/refactor/satisfy/find.go
generated
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
// Copyright 2014 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 satisfy inspects the type-checked ASTs of Go packages and
|
||||
// reports the set of discovered type constraints of the form (lhs, rhs
|
||||
// Type) where lhs is a non-trivial interface, rhs satisfies this
|
||||
// interface, and this fact is necessary for the package to be
|
||||
// well-typed.
|
||||
//
|
||||
// THIS PACKAGE IS EXPERIMENTAL AND MAY CHANGE AT ANY TIME.
|
||||
//
|
||||
// It is provided only for the gorename tool. Ideally this
|
||||
// functionality will become part of the type-checker in due course,
|
||||
// since it is computing it anyway, and it is robust for ill-typed
|
||||
// inputs, which this package is not.
|
||||
//
|
||||
package satisfy // import "golang.org/x/tools/refactor/satisfy"
|
||||
|
||||
// NOTES:
|
||||
//
|
||||
// We don't care about numeric conversions, so we don't descend into
|
||||
// types or constant expressions. This is unsound because
|
||||
// constant expressions can contain arbitrary statements, e.g.
|
||||
// const x = len([1]func(){func() {
|
||||
// ...
|
||||
// }})
|
||||
//
|
||||
// TODO(adonovan): make this robust against ill-typed input.
|
||||
// Or move it into the type-checker.
|
||||
//
|
||||
// Assignability conversions are possible in the following places:
|
||||
// - in assignments y = x, y := x, var y = x.
|
||||
// - from call argument types to formal parameter types
|
||||
// - in append and delete calls
|
||||
// - from return operands to result parameter types
|
||||
// - in composite literal T{k:v}, from k and v to T's field/element/key type
|
||||
// - in map[key] from key to the map's key type
|
||||
// - in comparisons x==y and switch x { case y: }.
|
||||
// - in explicit conversions T(x)
|
||||
// - in sends ch <- x, from x to the channel element type
|
||||
// - in type assertions x.(T) and switch x.(type) { case T: }
|
||||
//
|
||||
// The results of this pass provide information equivalent to the
|
||||
// ssa.MakeInterface and ssa.ChangeInterface instructions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// A Constraint records the fact that the RHS type does and must
|
||||
// satisify the LHS type, which is an interface.
|
||||
// The names are suggestive of an assignment statement LHS = RHS.
|
||||
type Constraint struct {
|
||||
LHS, RHS types.Type
|
||||
}
|
||||
|
||||
// A Finder inspects the type-checked ASTs of Go packages and
|
||||
// accumulates the set of type constraints (x, y) such that x is
|
||||
// assignable to y, y is an interface, and both x and y have methods.
|
||||
//
|
||||
// In other words, it returns the subset of the "implements" relation
|
||||
// that is checked during compilation of a package. Refactoring tools
|
||||
// will need to preserve at least this part of the relation to ensure
|
||||
// continued compilation.
|
||||
//
|
||||
type Finder struct {
|
||||
Result map[Constraint]bool
|
||||
msetcache typeutil.MethodSetCache
|
||||
|
||||
// per-Find state
|
||||
info *types.Info
|
||||
sig *types.Signature
|
||||
}
|
||||
|
||||
// Find inspects a single package, populating Result with its pairs of
|
||||
// constrained types.
|
||||
//
|
||||
// The result is non-canonical and thus may contain duplicates (but this
|
||||
// tends to preserves names of interface types better).
|
||||
//
|
||||
// The package must be free of type errors, and
|
||||
// info.{Defs,Uses,Selections,Types} must have been populated by the
|
||||
// type-checker.
|
||||
//
|
||||
func (f *Finder) Find(info *types.Info, files []*ast.File) {
|
||||
if f.Result == nil {
|
||||
f.Result = make(map[Constraint]bool)
|
||||
}
|
||||
|
||||
f.info = info
|
||||
for _, file := range files {
|
||||
for _, d := range file.Decls {
|
||||
switch d := d.(type) {
|
||||
case *ast.GenDecl:
|
||||
if d.Tok == token.VAR { // ignore consts
|
||||
for _, spec := range d.Specs {
|
||||
f.valueSpec(spec.(*ast.ValueSpec))
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if d.Body != nil {
|
||||
f.sig = f.info.Defs[d.Name].Type().(*types.Signature)
|
||||
f.stmt(d.Body)
|
||||
f.sig = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f.info = nil
|
||||
}
|
||||
|
||||
var (
|
||||
tInvalid = types.Typ[types.Invalid]
|
||||
tUntypedBool = types.Typ[types.UntypedBool]
|
||||
tUntypedNil = types.Typ[types.UntypedNil]
|
||||
)
|
||||
|
||||
// exprN visits an expression in a multi-value context.
|
||||
func (f *Finder) exprN(e ast.Expr) types.Type {
|
||||
typ := f.info.Types[e].Type.(*types.Tuple)
|
||||
switch e := e.(type) {
|
||||
case *ast.ParenExpr:
|
||||
return f.exprN(e.X)
|
||||
|
||||
case *ast.CallExpr:
|
||||
// x, err := f(args)
|
||||
sig := f.expr(e.Fun).Underlying().(*types.Signature)
|
||||
f.call(sig, e.Args)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
// y, ok := x[i]
|
||||
x := f.expr(e.X)
|
||||
f.assign(f.expr(e.Index), x.Underlying().(*types.Map).Key())
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
// y, ok := x.(T)
|
||||
f.typeAssert(f.expr(e.X), typ.At(0).Type())
|
||||
|
||||
case *ast.UnaryExpr: // must be receive <-
|
||||
// y, ok := <-x
|
||||
f.expr(e.X)
|
||||
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func (f *Finder) call(sig *types.Signature, args []ast.Expr) {
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Ellipsis call? e.g. f(x, y, z...)
|
||||
if _, ok := args[len(args)-1].(*ast.Ellipsis); ok {
|
||||
for i, arg := range args {
|
||||
// The final arg is a slice, and so is the final param.
|
||||
f.assign(sig.Params().At(i).Type(), f.expr(arg))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var argtypes []types.Type
|
||||
|
||||
// Gather the effective actual parameter types.
|
||||
if tuple, ok := f.info.Types[args[0]].Type.(*types.Tuple); ok {
|
||||
// f(g()) call where g has multiple results?
|
||||
f.expr(args[0])
|
||||
// unpack the tuple
|
||||
for i := 0; i < tuple.Len(); i++ {
|
||||
argtypes = append(argtypes, tuple.At(i).Type())
|
||||
}
|
||||
} else {
|
||||
for _, arg := range args {
|
||||
argtypes = append(argtypes, f.expr(arg))
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the actuals to the formals.
|
||||
if !sig.Variadic() {
|
||||
for i, argtype := range argtypes {
|
||||
f.assign(sig.Params().At(i).Type(), argtype)
|
||||
}
|
||||
} else {
|
||||
// The first n-1 parameters are assigned normally.
|
||||
nnormals := sig.Params().Len() - 1
|
||||
for i, argtype := range argtypes[:nnormals] {
|
||||
f.assign(sig.Params().At(i).Type(), argtype)
|
||||
}
|
||||
// Remaining args are assigned to elements of varargs slice.
|
||||
tElem := sig.Params().At(nnormals).Type().(*types.Slice).Elem()
|
||||
for i := nnormals; i < len(argtypes); i++ {
|
||||
f.assign(tElem, argtypes[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Finder) builtin(obj *types.Builtin, sig *types.Signature, args []ast.Expr, T types.Type) types.Type {
|
||||
switch obj.Name() {
|
||||
case "make", "new":
|
||||
// skip the type operand
|
||||
for _, arg := range args[1:] {
|
||||
f.expr(arg)
|
||||
}
|
||||
|
||||
case "append":
|
||||
s := f.expr(args[0])
|
||||
if _, ok := args[len(args)-1].(*ast.Ellipsis); ok && len(args) == 2 {
|
||||
// append(x, y...) including append([]byte, "foo"...)
|
||||
f.expr(args[1])
|
||||
} else {
|
||||
// append(x, y, z)
|
||||
tElem := s.Underlying().(*types.Slice).Elem()
|
||||
for _, arg := range args[1:] {
|
||||
f.assign(tElem, f.expr(arg))
|
||||
}
|
||||
}
|
||||
|
||||
case "delete":
|
||||
m := f.expr(args[0])
|
||||
k := f.expr(args[1])
|
||||
f.assign(m.Underlying().(*types.Map).Key(), k)
|
||||
|
||||
default:
|
||||
// ordinary call
|
||||
f.call(sig, args)
|
||||
}
|
||||
|
||||
return T
|
||||
}
|
||||
|
||||
func (f *Finder) extract(tuple types.Type, i int) types.Type {
|
||||
if tuple, ok := tuple.(*types.Tuple); ok && i < tuple.Len() {
|
||||
return tuple.At(i).Type()
|
||||
}
|
||||
return tInvalid
|
||||
}
|
||||
|
||||
func (f *Finder) valueSpec(spec *ast.ValueSpec) {
|
||||
var T types.Type
|
||||
if spec.Type != nil {
|
||||
T = f.info.Types[spec.Type].Type
|
||||
}
|
||||
switch len(spec.Values) {
|
||||
case len(spec.Names): // e.g. var x, y = f(), g()
|
||||
for _, value := range spec.Values {
|
||||
v := f.expr(value)
|
||||
if T != nil {
|
||||
f.assign(T, v)
|
||||
}
|
||||
}
|
||||
|
||||
case 1: // e.g. var x, y = f()
|
||||
tuple := f.exprN(spec.Values[0])
|
||||
for i := range spec.Names {
|
||||
if T != nil {
|
||||
f.assign(T, f.extract(tuple, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assign records pairs of distinct types that are related by
|
||||
// assignability, where the left-hand side is an interface and both
|
||||
// sides have methods.
|
||||
//
|
||||
// It should be called for all assignability checks, type assertions,
|
||||
// explicit conversions and comparisons between two types, unless the
|
||||
// types are uninteresting (e.g. lhs is a concrete type, or the empty
|
||||
// interface; rhs has no methods).
|
||||
//
|
||||
func (f *Finder) assign(lhs, rhs types.Type) {
|
||||
if types.Identical(lhs, rhs) {
|
||||
return
|
||||
}
|
||||
if !isInterface(lhs) {
|
||||
return
|
||||
}
|
||||
|
||||
if f.msetcache.MethodSet(lhs).Len() == 0 {
|
||||
return
|
||||
}
|
||||
if f.msetcache.MethodSet(rhs).Len() == 0 {
|
||||
return
|
||||
}
|
||||
// record the pair
|
||||
f.Result[Constraint{lhs, rhs}] = true
|
||||
}
|
||||
|
||||
// typeAssert must be called for each type assertion x.(T) where x has
|
||||
// interface type I.
|
||||
func (f *Finder) typeAssert(I, T types.Type) {
|
||||
// Type assertions are slightly subtle, because they are allowed
|
||||
// to be "impossible", e.g.
|
||||
//
|
||||
// var x interface{f()}
|
||||
// _ = x.(interface{f()int}) // legal
|
||||
//
|
||||
// (In hindsight, the language spec should probably not have
|
||||
// allowed this, but it's too late to fix now.)
|
||||
//
|
||||
// This means that a type assert from I to T isn't exactly a
|
||||
// constraint that T is assignable to I, but for a refactoring
|
||||
// tool it is a conditional constraint that, if T is assignable
|
||||
// to I before a refactoring, it should remain so after.
|
||||
|
||||
if types.AssignableTo(T, I) {
|
||||
f.assign(I, T)
|
||||
}
|
||||
}
|
||||
|
||||
// compare must be called for each comparison x==y.
|
||||
func (f *Finder) compare(x, y types.Type) {
|
||||
if types.AssignableTo(x, y) {
|
||||
f.assign(y, x)
|
||||
} else if types.AssignableTo(y, x) {
|
||||
f.assign(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// expr visits a true expression (not a type or defining ident)
|
||||
// and returns its type.
|
||||
func (f *Finder) expr(e ast.Expr) types.Type {
|
||||
tv := f.info.Types[e]
|
||||
if tv.Value != nil {
|
||||
return tv.Type // prune the descent for constants
|
||||
}
|
||||
|
||||
// tv.Type may be nil for an ast.Ident.
|
||||
|
||||
switch e := e.(type) {
|
||||
case *ast.BadExpr, *ast.BasicLit:
|
||||
// no-op
|
||||
|
||||
case *ast.Ident:
|
||||
// (referring idents only)
|
||||
if obj, ok := f.info.Uses[e]; ok {
|
||||
return obj.Type()
|
||||
}
|
||||
if e.Name == "_" { // e.g. "for _ = range x"
|
||||
return tInvalid
|
||||
}
|
||||
panic("undefined ident: " + e.Name)
|
||||
|
||||
case *ast.Ellipsis:
|
||||
if e.Elt != nil {
|
||||
f.expr(e.Elt)
|
||||
}
|
||||
|
||||
case *ast.FuncLit:
|
||||
saved := f.sig
|
||||
f.sig = tv.Type.(*types.Signature)
|
||||
f.stmt(e.Body)
|
||||
f.sig = saved
|
||||
|
||||
case *ast.CompositeLit:
|
||||
switch T := deref(tv.Type).Underlying().(type) {
|
||||
case *types.Struct:
|
||||
for i, elem := range e.Elts {
|
||||
if kv, ok := elem.(*ast.KeyValueExpr); ok {
|
||||
f.assign(f.info.Uses[kv.Key.(*ast.Ident)].Type(), f.expr(kv.Value))
|
||||
} else {
|
||||
f.assign(T.Field(i).Type(), f.expr(elem))
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Map:
|
||||
for _, elem := range e.Elts {
|
||||
elem := elem.(*ast.KeyValueExpr)
|
||||
f.assign(T.Key(), f.expr(elem.Key))
|
||||
f.assign(T.Elem(), f.expr(elem.Value))
|
||||
}
|
||||
|
||||
case *types.Array, *types.Slice:
|
||||
tElem := T.(interface {
|
||||
Elem() types.Type
|
||||
}).Elem()
|
||||
for _, elem := range e.Elts {
|
||||
if kv, ok := elem.(*ast.KeyValueExpr); ok {
|
||||
// ignore the key
|
||||
f.assign(tElem, f.expr(kv.Value))
|
||||
} else {
|
||||
f.assign(tElem, f.expr(elem))
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unexpected composite literal type: " + tv.Type.String())
|
||||
}
|
||||
|
||||
case *ast.ParenExpr:
|
||||
f.expr(e.X)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
if _, ok := f.info.Selections[e]; ok {
|
||||
f.expr(e.X) // selection
|
||||
} else {
|
||||
return f.info.Uses[e.Sel].Type() // qualified identifier
|
||||
}
|
||||
|
||||
case *ast.IndexExpr:
|
||||
x := f.expr(e.X)
|
||||
i := f.expr(e.Index)
|
||||
if ux, ok := x.Underlying().(*types.Map); ok {
|
||||
f.assign(ux.Key(), i)
|
||||
}
|
||||
|
||||
case *ast.SliceExpr:
|
||||
f.expr(e.X)
|
||||
if e.Low != nil {
|
||||
f.expr(e.Low)
|
||||
}
|
||||
if e.High != nil {
|
||||
f.expr(e.High)
|
||||
}
|
||||
if e.Max != nil {
|
||||
f.expr(e.Max)
|
||||
}
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
x := f.expr(e.X)
|
||||
f.typeAssert(x, f.info.Types[e.Type].Type)
|
||||
|
||||
case *ast.CallExpr:
|
||||
if tvFun := f.info.Types[e.Fun]; tvFun.IsType() {
|
||||
// conversion
|
||||
arg0 := f.expr(e.Args[0])
|
||||
f.assign(tvFun.Type, arg0)
|
||||
} else {
|
||||
// function call
|
||||
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
|
||||
if obj, ok := f.info.Uses[id].(*types.Builtin); ok {
|
||||
sig := f.info.Types[id].Type.(*types.Signature)
|
||||
return f.builtin(obj, sig, e.Args, tv.Type)
|
||||
}
|
||||
}
|
||||
// ordinary call
|
||||
f.call(f.expr(e.Fun).Underlying().(*types.Signature), e.Args)
|
||||
}
|
||||
|
||||
case *ast.StarExpr:
|
||||
f.expr(e.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
f.expr(e.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
x := f.expr(e.X)
|
||||
y := f.expr(e.Y)
|
||||
if e.Op == token.EQL || e.Op == token.NEQ {
|
||||
f.compare(x, y)
|
||||
}
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
f.expr(e.Key)
|
||||
f.expr(e.Value)
|
||||
|
||||
case *ast.ArrayType,
|
||||
*ast.StructType,
|
||||
*ast.FuncType,
|
||||
*ast.InterfaceType,
|
||||
*ast.MapType,
|
||||
*ast.ChanType:
|
||||
panic(e)
|
||||
}
|
||||
|
||||
if tv.Type == nil {
|
||||
panic(fmt.Sprintf("no type for %T", e))
|
||||
}
|
||||
|
||||
return tv.Type
|
||||
}
|
||||
|
||||
func (f *Finder) stmt(s ast.Stmt) {
|
||||
switch s := s.(type) {
|
||||
case *ast.BadStmt,
|
||||
*ast.EmptyStmt,
|
||||
*ast.BranchStmt:
|
||||
// no-op
|
||||
|
||||
case *ast.DeclStmt:
|
||||
d := s.Decl.(*ast.GenDecl)
|
||||
if d.Tok == token.VAR { // ignore consts
|
||||
for _, spec := range d.Specs {
|
||||
f.valueSpec(spec.(*ast.ValueSpec))
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
f.stmt(s.Stmt)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
f.expr(s.X)
|
||||
|
||||
case *ast.SendStmt:
|
||||
ch := f.expr(s.Chan)
|
||||
val := f.expr(s.Value)
|
||||
f.assign(ch.Underlying().(*types.Chan).Elem(), val)
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
f.expr(s.X)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
switch s.Tok {
|
||||
case token.ASSIGN, token.DEFINE:
|
||||
// y := x or y = x
|
||||
var rhsTuple types.Type
|
||||
if len(s.Lhs) != len(s.Rhs) {
|
||||
rhsTuple = f.exprN(s.Rhs[0])
|
||||
}
|
||||
for i := range s.Lhs {
|
||||
var lhs, rhs types.Type
|
||||
if rhsTuple == nil {
|
||||
rhs = f.expr(s.Rhs[i]) // 1:1 assignment
|
||||
} else {
|
||||
rhs = f.extract(rhsTuple, i) // n:1 assignment
|
||||
}
|
||||
|
||||
if id, ok := s.Lhs[i].(*ast.Ident); ok {
|
||||
if id.Name != "_" {
|
||||
if obj, ok := f.info.Defs[id]; ok {
|
||||
lhs = obj.Type() // definition
|
||||
}
|
||||
}
|
||||
}
|
||||
if lhs == nil {
|
||||
lhs = f.expr(s.Lhs[i]) // assignment
|
||||
}
|
||||
f.assign(lhs, rhs)
|
||||
}
|
||||
|
||||
default:
|
||||
// y op= x
|
||||
f.expr(s.Lhs[0])
|
||||
f.expr(s.Rhs[0])
|
||||
}
|
||||
|
||||
case *ast.GoStmt:
|
||||
f.expr(s.Call)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
f.expr(s.Call)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
formals := f.sig.Results()
|
||||
switch len(s.Results) {
|
||||
case formals.Len(): // 1:1
|
||||
for i, result := range s.Results {
|
||||
f.assign(formals.At(i).Type(), f.expr(result))
|
||||
}
|
||||
|
||||
case 1: // n:1
|
||||
tuple := f.exprN(s.Results[0])
|
||||
for i := 0; i < formals.Len(); i++ {
|
||||
f.assign(formals.At(i).Type(), f.extract(tuple, i))
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.SelectStmt:
|
||||
f.stmt(s.Body)
|
||||
|
||||
case *ast.BlockStmt:
|
||||
for _, s := range s.List {
|
||||
f.stmt(s)
|
||||
}
|
||||
|
||||
case *ast.IfStmt:
|
||||
if s.Init != nil {
|
||||
f.stmt(s.Init)
|
||||
}
|
||||
f.expr(s.Cond)
|
||||
f.stmt(s.Body)
|
||||
if s.Else != nil {
|
||||
f.stmt(s.Else)
|
||||
}
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
if s.Init != nil {
|
||||
f.stmt(s.Init)
|
||||
}
|
||||
var tag types.Type = tUntypedBool
|
||||
if s.Tag != nil {
|
||||
tag = f.expr(s.Tag)
|
||||
}
|
||||
for _, cc := range s.Body.List {
|
||||
cc := cc.(*ast.CaseClause)
|
||||
for _, cond := range cc.List {
|
||||
f.compare(tag, f.info.Types[cond].Type)
|
||||
}
|
||||
for _, s := range cc.Body {
|
||||
f.stmt(s)
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
if s.Init != nil {
|
||||
f.stmt(s.Init)
|
||||
}
|
||||
var I types.Type
|
||||
switch ass := s.Assign.(type) {
|
||||
case *ast.ExprStmt: // x.(type)
|
||||
I = f.expr(unparen(ass.X).(*ast.TypeAssertExpr).X)
|
||||
case *ast.AssignStmt: // y := x.(type)
|
||||
I = f.expr(unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X)
|
||||
}
|
||||
for _, cc := range s.Body.List {
|
||||
cc := cc.(*ast.CaseClause)
|
||||
for _, cond := range cc.List {
|
||||
tCase := f.info.Types[cond].Type
|
||||
if tCase != tUntypedNil {
|
||||
f.typeAssert(I, tCase)
|
||||
}
|
||||
}
|
||||
for _, s := range cc.Body {
|
||||
f.stmt(s)
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.CommClause:
|
||||
if s.Comm != nil {
|
||||
f.stmt(s.Comm)
|
||||
}
|
||||
for _, s := range s.Body {
|
||||
f.stmt(s)
|
||||
}
|
||||
|
||||
case *ast.ForStmt:
|
||||
if s.Init != nil {
|
||||
f.stmt(s.Init)
|
||||
}
|
||||
if s.Cond != nil {
|
||||
f.expr(s.Cond)
|
||||
}
|
||||
if s.Post != nil {
|
||||
f.stmt(s.Post)
|
||||
}
|
||||
f.stmt(s.Body)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
x := f.expr(s.X)
|
||||
// No conversions are involved when Tok==DEFINE.
|
||||
if s.Tok == token.ASSIGN {
|
||||
if s.Key != nil {
|
||||
k := f.expr(s.Key)
|
||||
var xelem types.Type
|
||||
// keys of array, *array, slice, string aren't interesting
|
||||
switch ux := x.Underlying().(type) {
|
||||
case *types.Chan:
|
||||
xelem = ux.Elem()
|
||||
case *types.Map:
|
||||
xelem = ux.Key()
|
||||
}
|
||||
if xelem != nil {
|
||||
f.assign(xelem, k)
|
||||
}
|
||||
}
|
||||
if s.Value != nil {
|
||||
val := f.expr(s.Value)
|
||||
var xelem types.Type
|
||||
// values of strings aren't interesting
|
||||
switch ux := x.Underlying().(type) {
|
||||
case *types.Array:
|
||||
xelem = ux.Elem()
|
||||
case *types.Chan:
|
||||
xelem = ux.Elem()
|
||||
case *types.Map:
|
||||
xelem = ux.Elem()
|
||||
case *types.Pointer: // *array
|
||||
xelem = deref(ux).(*types.Array).Elem()
|
||||
case *types.Slice:
|
||||
xelem = ux.Elem()
|
||||
}
|
||||
if xelem != nil {
|
||||
f.assign(xelem, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.stmt(s.Body)
|
||||
|
||||
default:
|
||||
panic(s)
|
||||
}
|
||||
}
|
||||
|
||||
// -- Plundered from golang.org/x/tools/go/ssa -----------------
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
Reference in New Issue
Block a user