github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/freevars.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"go/ast"
    10  	"go/printer"
    11  	"go/token"
    12  	"go/types"
    13  	"sort"
    14  
    15  	"github.com/april1989/origin-go-tools/cmd/guru/serial"
    16  	"github.com/april1989/origin-go-tools/go/loader"
    17  )
    18  
    19  // freevars displays the lexical (not package-level) free variables of
    20  // the selection.
    21  //
    22  // It treats A.B.C as a separate variable from A to reveal the parts
    23  // of an aggregate type that are actually needed.
    24  // This aids refactoring.
    25  //
    26  // TODO(adonovan): optionally display the free references to
    27  // file/package scope objects, and to objects from other packages.
    28  // Depending on where the resulting function abstraction will go,
    29  // these might be interesting.  Perhaps group the results into three
    30  // bands.
    31  //
    32  func freevars(q *Query) error {
    33  	lconf := loader.Config{Build: q.Build}
    34  	allowErrors(&lconf)
    35  
    36  	if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
    37  		return err
    38  	}
    39  
    40  	// Load/parse/type-check the program.
    41  	lprog, err := lconf.Load()
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	qpos, err := parseQueryPos(lprog, q.Pos, false)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	file := qpos.path[len(qpos.path)-1] // the enclosing file
    52  	fileScope := qpos.info.Scopes[file]
    53  	pkgScope := fileScope.Parent()
    54  
    55  	// The id and sel functions return non-nil if they denote an
    56  	// object o or selection o.x.y that is referenced by the
    57  	// selection but defined neither within the selection nor at
    58  	// file scope, i.e. it is in the lexical environment.
    59  	var id func(n *ast.Ident) types.Object
    60  	var sel func(n *ast.SelectorExpr) types.Object
    61  
    62  	sel = func(n *ast.SelectorExpr) types.Object {
    63  		switch x := unparen(n.X).(type) {
    64  		case *ast.SelectorExpr:
    65  			return sel(x)
    66  		case *ast.Ident:
    67  			return id(x)
    68  		}
    69  		return nil
    70  	}
    71  
    72  	id = func(n *ast.Ident) types.Object {
    73  		obj := qpos.info.Uses[n]
    74  		if obj == nil {
    75  			return nil // not a reference
    76  		}
    77  		if _, ok := obj.(*types.PkgName); ok {
    78  			return nil // imported package
    79  		}
    80  		if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
    81  			return nil // not defined in this file
    82  		}
    83  		scope := obj.Parent()
    84  		if scope == nil {
    85  			return nil // e.g. interface method, struct field
    86  		}
    87  		if scope == fileScope || scope == pkgScope {
    88  			return nil // defined at file or package scope
    89  		}
    90  		if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end {
    91  			return nil // defined within selection => not free
    92  		}
    93  		return obj
    94  	}
    95  
    96  	// Maps each reference that is free in the selection
    97  	// to the object it refers to.
    98  	// The map de-duplicates repeated references.
    99  	refsMap := make(map[string]freevarsRef)
   100  
   101  	// Visit all the identifiers in the selected ASTs.
   102  	ast.Inspect(qpos.path[0], func(n ast.Node) bool {
   103  		if n == nil {
   104  			return true // popping DFS stack
   105  		}
   106  
   107  		// Is this node contained within the selection?
   108  		// (freevars permits inexact selections,
   109  		// like two stmts in a block.)
   110  		if qpos.start <= n.Pos() && n.End() <= qpos.end {
   111  			var obj types.Object
   112  			var prune bool
   113  			switch n := n.(type) {
   114  			case *ast.Ident:
   115  				obj = id(n)
   116  
   117  			case *ast.SelectorExpr:
   118  				obj = sel(n)
   119  				prune = true
   120  			}
   121  
   122  			if obj != nil {
   123  				var kind string
   124  				switch obj.(type) {
   125  				case *types.Var:
   126  					kind = "var"
   127  				case *types.Func:
   128  					kind = "func"
   129  				case *types.TypeName:
   130  					kind = "type"
   131  				case *types.Const:
   132  					kind = "const"
   133  				case *types.Label:
   134  					kind = "label"
   135  				default:
   136  					panic(obj)
   137  				}
   138  
   139  				typ := qpos.info.TypeOf(n.(ast.Expr))
   140  				ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
   141  				refsMap[ref.ref] = ref
   142  
   143  				if prune {
   144  					return false // don't descend
   145  				}
   146  			}
   147  		}
   148  
   149  		return true // descend
   150  	})
   151  
   152  	refs := make([]freevarsRef, 0, len(refsMap))
   153  	for _, ref := range refsMap {
   154  		refs = append(refs, ref)
   155  	}
   156  	sort.Sort(byRef(refs))
   157  
   158  	q.Output(lprog.Fset, &freevarsResult{
   159  		qpos: qpos,
   160  		refs: refs,
   161  	})
   162  	return nil
   163  }
   164  
   165  type freevarsResult struct {
   166  	qpos *queryPos
   167  	refs []freevarsRef
   168  }
   169  
   170  type freevarsRef struct {
   171  	kind string
   172  	ref  string
   173  	typ  types.Type
   174  	obj  types.Object
   175  }
   176  
   177  func (r *freevarsResult) PrintPlain(printf printfFunc) {
   178  	if len(r.refs) == 0 {
   179  		printf(r.qpos, "No free identifiers.")
   180  	} else {
   181  		printf(r.qpos, "Free identifiers:")
   182  		qualifier := types.RelativeTo(r.qpos.info.Pkg)
   183  		for _, ref := range r.refs {
   184  			// Avoid printing "type T T".
   185  			var typstr string
   186  			if ref.kind != "type" && ref.kind != "label" {
   187  				typstr = " " + types.TypeString(ref.typ, qualifier)
   188  			}
   189  			printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
   190  		}
   191  	}
   192  }
   193  
   194  func (r *freevarsResult) JSON(fset *token.FileSet) []byte {
   195  	var buf bytes.Buffer
   196  	for i, ref := range r.refs {
   197  		if i > 0 {
   198  			buf.WriteByte('\n')
   199  		}
   200  		buf.Write(toJSON(serial.FreeVar{
   201  			Pos:  fset.Position(ref.obj.Pos()).String(),
   202  			Kind: ref.kind,
   203  			Ref:  ref.ref,
   204  			Type: ref.typ.String(),
   205  		}))
   206  	}
   207  	return buf.Bytes()
   208  }
   209  
   210  // -------- utils --------
   211  
   212  type byRef []freevarsRef
   213  
   214  func (p byRef) Len() int           { return len(p) }
   215  func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
   216  func (p byRef) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
   217  
   218  // printNode returns the pretty-printed syntax of n.
   219  func printNode(fset *token.FileSet, n ast.Node) string {
   220  	var buf bytes.Buffer
   221  	printer.Fprint(&buf, fset, n)
   222  	return buf.String()
   223  }