| // Copyright 2017, 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.md file. |
| |
| package cmpopts |
| |
| import ( |
| "fmt" |
| "reflect" |
| "unicode" |
| "unicode/utf8" |
| |
| "github.com/google/go-cmp/cmp" |
| ) |
| |
| // IgnoreFields returns an Option that ignores exported fields of the |
| // given names on a single struct type. |
| // The struct type is specified by passing in a value of that type. |
| // |
| // The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a |
| // specific sub-field that is embedded or nested within the parent struct. |
| // |
| // This does not handle unexported fields; use IgnoreUnexported instead. |
| func IgnoreFields(typ interface{}, names ...string) cmp.Option { |
| sf := newStructFilter(typ, names...) |
| return cmp.FilterPath(sf.filter, cmp.Ignore()) |
| } |
| |
| // IgnoreTypes returns an Option that ignores all values assignable to |
| // certain types, which are specified by passing in a value of each type. |
| func IgnoreTypes(typs ...interface{}) cmp.Option { |
| tf := newTypeFilter(typs...) |
| return cmp.FilterPath(tf.filter, cmp.Ignore()) |
| } |
| |
| type typeFilter []reflect.Type |
| |
| func newTypeFilter(typs ...interface{}) (tf typeFilter) { |
| for _, typ := range typs { |
| t := reflect.TypeOf(typ) |
| if t == nil { |
| // This occurs if someone tries to pass in sync.Locker(nil) |
| panic("cannot determine type; consider using IgnoreInterfaces") |
| } |
| tf = append(tf, t) |
| } |
| return tf |
| } |
| func (tf typeFilter) filter(p cmp.Path) bool { |
| if len(p) < 1 { |
| return false |
| } |
| t := p[len(p)-1].Type() |
| for _, ti := range tf { |
| if t.AssignableTo(ti) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // IgnoreInterfaces returns an Option that ignores all values or references of |
| // values assignable to certain interface types. These interfaces are specified |
| // by passing in an anonymous struct with the interface types embedded in it. |
| // For example, to ignore sync.Locker, pass in struct{sync.Locker}{}. |
| func IgnoreInterfaces(ifaces interface{}) cmp.Option { |
| tf := newIfaceFilter(ifaces) |
| return cmp.FilterPath(tf.filter, cmp.Ignore()) |
| } |
| |
| type ifaceFilter []reflect.Type |
| |
| func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) { |
| t := reflect.TypeOf(ifaces) |
| if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct { |
| panic("input must be an anonymous struct") |
| } |
| for i := 0; i < t.NumField(); i++ { |
| fi := t.Field(i) |
| switch { |
| case !fi.Anonymous: |
| panic("struct cannot have named fields") |
| case fi.Type.Kind() != reflect.Interface: |
| panic("embedded field must be an interface type") |
| case fi.Type.NumMethod() == 0: |
| // This matches everything; why would you ever want this? |
| panic("cannot ignore empty interface") |
| default: |
| tf = append(tf, fi.Type) |
| } |
| } |
| return tf |
| } |
| func (tf ifaceFilter) filter(p cmp.Path) bool { |
| if len(p) < 1 { |
| return false |
| } |
| t := p[len(p)-1].Type() |
| for _, ti := range tf { |
| if t.AssignableTo(ti) { |
| return true |
| } |
| if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // IgnoreUnexported returns an Option that only ignores the immediate unexported |
| // fields of a struct, including anonymous fields of unexported types. |
| // In particular, unexported fields within the struct's exported fields |
| // of struct types, including anonymous fields, will not be ignored unless the |
| // type of the field itself is also passed to IgnoreUnexported. |
| func IgnoreUnexported(typs ...interface{}) cmp.Option { |
| ux := newUnexportedFilter(typs...) |
| return cmp.FilterPath(ux.filter, cmp.Ignore()) |
| } |
| |
| type unexportedFilter struct{ m map[reflect.Type]bool } |
| |
| func newUnexportedFilter(typs ...interface{}) unexportedFilter { |
| ux := unexportedFilter{m: make(map[reflect.Type]bool)} |
| for _, typ := range typs { |
| t := reflect.TypeOf(typ) |
| if t == nil || t.Kind() != reflect.Struct { |
| panic(fmt.Sprintf("invalid struct type: %T", typ)) |
| } |
| ux.m[t] = true |
| } |
| return ux |
| } |
| func (xf unexportedFilter) filter(p cmp.Path) bool { |
| if len(p) < 2 { |
| return false |
| } |
| sf, ok := p[len(p)-1].(cmp.StructField) |
| if !ok { |
| return false |
| } |
| return xf.m[p[len(p)-2].Type()] && !isExported(sf.Name()) |
| } |
| |
| // isExported reports whether the identifier is exported. |
| func isExported(id string) bool { |
| r, _ := utf8.DecodeRuneInString(id) |
| return unicode.IsUpper(r) |
| } |