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