gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/go/packages/packagestest/expect.go (about)

     1  // Copyright 2018 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 packagestest
     6  
     7  import (
     8  	"fmt"
     9  	"go/token"
    10  	"path/filepath"
    11  	"reflect"
    12  	"regexp"
    13  
    14  	"golang.org/x/tools/go/expect"
    15  	"golang.org/x/tools/go/packages"
    16  )
    17  
    18  const (
    19  	markMethod    = "mark"
    20  	eofIdentifier = "EOF"
    21  )
    22  
    23  // Expect invokes the supplied methods for all expectation notes found in
    24  // the exported source files.
    25  //
    26  // All exported go source files are parsed to collect the expectation
    27  // notes.
    28  // See the documentation for expect.Parse for how the notes are collected
    29  // and parsed.
    30  //
    31  // The methods are supplied as a map of name to function, and those functions
    32  // will be matched against the expectations by name.
    33  // Notes with no matching function will be skipped, and functions with no
    34  // matching notes will not be invoked.
    35  // If there are no registered markers yet, a special pass will be run first
    36  // which adds any markers declared with @mark(Name, pattern) or @name. These
    37  // call the Mark method to add the marker to the global set.
    38  // You can register the "mark" method to override these in your own call to
    39  // Expect. The bound Mark function is usable directly in your method map, so
    40  //    exported.Expect(map[string]interface{}{"mark": exported.Mark})
    41  // replicates the built in behavior.
    42  //
    43  // Method invocation
    44  //
    45  // When invoking a method the expressions in the parameter list need to be
    46  // converted to values to be passed to the method.
    47  // There are a very limited set of types the arguments are allowed to be.
    48  //   expect.Note : passed the Note instance being evaluated.
    49  //   string : can be supplied either a string literal or an identifier.
    50  //   int : can only be supplied an integer literal.
    51  //   *regexp.Regexp : can only be supplied a regular expression literal
    52  //   token.Pos : has a file position calculated as described below.
    53  //   token.Position : has a file position calculated as described below.
    54  //   interface{} : will be passed any value
    55  //
    56  // Position calculation
    57  //
    58  // There is some extra handling when a parameter is being coerced into a
    59  // token.Pos, token.Position or Range type argument.
    60  //
    61  // If the parameter is an identifier, it will be treated as the name of an
    62  // marker to look up (as if markers were global variables).
    63  //
    64  // If it is a string or regular expression, then it will be passed to
    65  // expect.MatchBefore to look up a match in the line at which it was declared.
    66  //
    67  // It is safe to call this repeatedly with different method sets, but it is
    68  // not safe to call it concurrently.
    69  func (e *Exported) Expect(methods map[string]interface{}) error {
    70  	if err := e.getNotes(); err != nil {
    71  		return err
    72  	}
    73  	if err := e.getMarkers(); err != nil {
    74  		return err
    75  	}
    76  	var err error
    77  	ms := make(map[string]method, len(methods))
    78  	for name, f := range methods {
    79  		mi := method{f: reflect.ValueOf(f)}
    80  		mi.converters = make([]converter, mi.f.Type().NumIn())
    81  		for i := 0; i < len(mi.converters); i++ {
    82  			mi.converters[i], err = e.buildConverter(mi.f.Type().In(i))
    83  			if err != nil {
    84  				return fmt.Errorf("invalid method %v: %v", name, err)
    85  			}
    86  		}
    87  		ms[name] = mi
    88  	}
    89  	for _, n := range e.notes {
    90  		if n.Args == nil {
    91  			// simple identifier form, convert to a call to mark
    92  			n = &expect.Note{
    93  				Pos:  n.Pos,
    94  				Name: markMethod,
    95  				Args: []interface{}{n.Name, n.Name},
    96  			}
    97  		}
    98  		mi, ok := ms[n.Name]
    99  		if !ok {
   100  			continue
   101  		}
   102  		params := make([]reflect.Value, len(mi.converters))
   103  		args := n.Args
   104  		for i, convert := range mi.converters {
   105  			params[i], args, err = convert(n, args)
   106  			if err != nil {
   107  				return fmt.Errorf("%v: %v", e.fset.Position(n.Pos), err)
   108  			}
   109  		}
   110  		if len(args) > 0 {
   111  			return fmt.Errorf("%v: unwanted args got %+v extra", e.fset.Position(n.Pos), args)
   112  		}
   113  		//TODO: catch the error returned from the method
   114  		mi.f.Call(params)
   115  	}
   116  	return nil
   117  }
   118  
   119  type Range struct {
   120  	Start token.Pos
   121  	End   token.Pos
   122  }
   123  
   124  // Mark adds a new marker to the known set.
   125  func (e *Exported) Mark(name string, r Range) {
   126  	if e.markers == nil {
   127  		e.markers = make(map[string]Range)
   128  	}
   129  	e.markers[name] = r
   130  }
   131  
   132  func (e *Exported) getNotes() error {
   133  	if e.notes != nil {
   134  		return nil
   135  	}
   136  	notes := []*expect.Note{}
   137  	var dirs []string
   138  	for _, module := range e.written {
   139  		for _, filename := range module {
   140  			dirs = append(dirs, filepath.Dir(filename))
   141  		}
   142  	}
   143  	pkgs, err := packages.Load(e.Config, dirs...)
   144  	if err != nil {
   145  		return fmt.Errorf("unable to load packages for directories %s: %v", dirs, err)
   146  	}
   147  	for _, pkg := range pkgs {
   148  		for _, filename := range pkg.GoFiles {
   149  			content, err := e.FileContents(filename)
   150  			if err != nil {
   151  				return err
   152  			}
   153  			l, err := expect.Parse(e.fset, filename, content)
   154  			if err != nil {
   155  				return fmt.Errorf("Failed to extract expectations: %v", err)
   156  			}
   157  			notes = append(notes, l...)
   158  		}
   159  	}
   160  	e.notes = notes
   161  	return nil
   162  }
   163  
   164  func (e *Exported) getMarkers() error {
   165  	if e.markers != nil {
   166  		return nil
   167  	}
   168  	// set markers early so that we don't call getMarkers again from Expect
   169  	e.markers = make(map[string]Range)
   170  	return e.Expect(map[string]interface{}{
   171  		markMethod: e.Mark,
   172  	})
   173  }
   174  
   175  var (
   176  	noteType       = reflect.TypeOf((*expect.Note)(nil))
   177  	identifierType = reflect.TypeOf(expect.Identifier(""))
   178  	posType        = reflect.TypeOf(token.Pos(0))
   179  	positionType   = reflect.TypeOf(token.Position{})
   180  	rangeType      = reflect.TypeOf(Range{})
   181  	fsetType       = reflect.TypeOf((*token.FileSet)(nil))
   182  	regexType      = reflect.TypeOf((*regexp.Regexp)(nil))
   183  )
   184  
   185  // converter converts from a marker's argument parsed from the comment to
   186  // reflect values passed to the method during Invoke.
   187  // It takes the args remaining, and returns the args it did not consume.
   188  // This allows a converter to consume 0 args for well known types, or multiple
   189  // args for compound types.
   190  type converter func(*expect.Note, []interface{}) (reflect.Value, []interface{}, error)
   191  
   192  // method is used to track information about Invoke methods that is expensive to
   193  // calculate so that we can work it out once rather than per marker.
   194  type method struct {
   195  	f          reflect.Value // the reflect value of the passed in method
   196  	converters []converter   // the parameter converters for the method
   197  }
   198  
   199  // buildConverter works out what function should be used to go from an ast expressions to a reflect
   200  // value of the type expected by a method.
   201  // It is called when only the target type is know, it returns converters that are flexible across
   202  // all supported expression types for that target type.
   203  func (e *Exported) buildConverter(pt reflect.Type) (converter, error) {
   204  	switch {
   205  	case pt == noteType:
   206  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   207  			return reflect.ValueOf(n), args, nil
   208  		}, nil
   209  	case pt == fsetType:
   210  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   211  			return reflect.ValueOf(e.fset), args, nil
   212  		}, nil
   213  	case pt == posType:
   214  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   215  			r, remains, err := e.rangeConverter(n, args)
   216  			if err != nil {
   217  				return reflect.Value{}, nil, err
   218  			}
   219  			return reflect.ValueOf(r.Start), remains, nil
   220  		}, nil
   221  	case pt == positionType:
   222  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   223  			r, remains, err := e.rangeConverter(n, args)
   224  			if err != nil {
   225  				return reflect.Value{}, nil, err
   226  			}
   227  			return reflect.ValueOf(e.fset.Position(r.Start)), remains, nil
   228  		}, nil
   229  	case pt == rangeType:
   230  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   231  			r, remains, err := e.rangeConverter(n, args)
   232  			if err != nil {
   233  				return reflect.Value{}, nil, err
   234  			}
   235  			return reflect.ValueOf(r), remains, nil
   236  		}, nil
   237  	case pt == identifierType:
   238  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   239  			arg := args[0]
   240  			args = args[1:]
   241  			switch arg := arg.(type) {
   242  			case expect.Identifier:
   243  				return reflect.ValueOf(arg), args, nil
   244  			default:
   245  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to string", arg)
   246  			}
   247  		}, nil
   248  
   249  	case pt == regexType:
   250  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   251  			arg := args[0]
   252  			args = args[1:]
   253  			if _, ok := arg.(*regexp.Regexp); !ok {
   254  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to *regexp.Regexp", arg)
   255  			}
   256  			return reflect.ValueOf(arg), args, nil
   257  		}, nil
   258  
   259  	case pt.Kind() == reflect.String:
   260  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   261  			arg := args[0]
   262  			args = args[1:]
   263  			switch arg := arg.(type) {
   264  			case expect.Identifier:
   265  				return reflect.ValueOf(string(arg)), args, nil
   266  			case string:
   267  				return reflect.ValueOf(arg), args, nil
   268  			default:
   269  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to string", arg)
   270  			}
   271  		}, nil
   272  	case pt.Kind() == reflect.Int64:
   273  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   274  			arg := args[0]
   275  			args = args[1:]
   276  			switch arg := arg.(type) {
   277  			case int64:
   278  				return reflect.ValueOf(arg), args, nil
   279  			default:
   280  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to int", arg)
   281  			}
   282  		}, nil
   283  	case pt.Kind() == reflect.Bool:
   284  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   285  			arg := args[0]
   286  			args = args[1:]
   287  			b, ok := arg.(bool)
   288  			if !ok {
   289  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to bool", arg)
   290  			}
   291  			return reflect.ValueOf(b), args, nil
   292  		}, nil
   293  	case pt.Kind() == reflect.Slice:
   294  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   295  			converter, err := e.buildConverter(pt.Elem())
   296  			if err != nil {
   297  				return reflect.Value{}, nil, err
   298  			}
   299  			result := reflect.MakeSlice(reflect.SliceOf(pt.Elem()), 0, len(args))
   300  			for range args {
   301  				value, remains, err := converter(n, args)
   302  				if err != nil {
   303  					return reflect.Value{}, nil, err
   304  				}
   305  				result = reflect.Append(result, value)
   306  				args = remains
   307  			}
   308  			return result, args, nil
   309  		}, nil
   310  	default:
   311  		if pt.Kind() == reflect.Interface && pt.NumMethod() == 0 {
   312  			return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   313  				return reflect.ValueOf(args[0]), args[1:], nil
   314  			}, nil
   315  		}
   316  		return nil, fmt.Errorf("param has unexpected type %v (kind %v)", pt, pt.Kind())
   317  	}
   318  }
   319  
   320  func (e *Exported) rangeConverter(n *expect.Note, args []interface{}) (Range, []interface{}, error) {
   321  	if len(args) < 1 {
   322  		return Range{}, nil, fmt.Errorf("missing argument")
   323  	}
   324  	arg := args[0]
   325  	args = args[1:]
   326  	switch arg := arg.(type) {
   327  	case expect.Identifier:
   328  		// handle the special identifiers
   329  		switch arg {
   330  		case eofIdentifier:
   331  			// end of file identifier, look up the current file
   332  			f := e.fset.File(n.Pos)
   333  			eof := f.Pos(f.Size())
   334  			return Range{Start: eof, End: token.NoPos}, args, nil
   335  		default:
   336  			// look up an marker by name
   337  			mark, ok := e.markers[string(arg)]
   338  			if !ok {
   339  				return Range{}, nil, fmt.Errorf("cannot find marker %v", arg)
   340  			}
   341  			return mark, args, nil
   342  		}
   343  	case string:
   344  		start, end, err := expect.MatchBefore(e.fset, e.FileContents, n.Pos, arg)
   345  		if err != nil {
   346  			return Range{}, nil, err
   347  		}
   348  		if start == token.NoPos {
   349  			return Range{}, nil, fmt.Errorf("%v: pattern %s did not match", e.fset.Position(n.Pos), arg)
   350  		}
   351  		return Range{Start: start, End: end}, args, nil
   352  	case *regexp.Regexp:
   353  		start, end, err := expect.MatchBefore(e.fset, e.FileContents, n.Pos, arg)
   354  		if err != nil {
   355  			return Range{}, nil, err
   356  		}
   357  		if start == token.NoPos {
   358  			return Range{}, nil, fmt.Errorf("%v: pattern %s did not match", e.fset.Position(n.Pos), arg)
   359  		}
   360  		return Range{Start: start, End: end}, args, nil
   361  	default:
   362  		return Range{}, nil, fmt.Errorf("cannot convert %v to pos", arg)
   363  	}
   364  }