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 }