132 lines
4.6 KiB
Go
132 lines
4.6 KiB
Go
/*
|
|
Copyright 2019 The Crossplane Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package types contains utilities for manipulating structs.
|
|
package types
|
|
|
|
import (
|
|
"go/types"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/crossplane/crossplane-tools/internal/comments"
|
|
)
|
|
|
|
// NamedProcessorChain runs multiple NamedProcessors in order.
|
|
type NamedProcessorChain []NamedProcessor
|
|
|
|
// Process run Process method of all NamedProcessors.
|
|
func (tpc NamedProcessorChain) Process(n *types.Named, comment string) error {
|
|
for i, tp := range tpc {
|
|
if err := tp.Process(n, comment); err != nil {
|
|
return errors.Errorf("type processor at index %d failed", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NamedProcessor takes a named struct with its comments and processes it. For
|
|
// example, you might want to take an action depending on its comments or make
|
|
// an in-place change on the *types.Named object if it satisfies certain criteria.
|
|
type NamedProcessor interface {
|
|
Process(n *types.Named, comment string) error
|
|
}
|
|
|
|
// FieldProcessorChain runs multiple FieldProcessor in order.
|
|
type FieldProcessorChain []FieldProcessor
|
|
|
|
// Process run Process method of all FieldProcessor.
|
|
func (fpc FieldProcessorChain) Process(n *types.Named, f *types.Var, tag, comment string, parentFields ...string) error {
|
|
for i, fp := range fpc {
|
|
if err := fp.Process(n, f, tag, comment, parentFields...); err != nil {
|
|
return errors.Errorf("field processor at index %d failed", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FieldProcessor takes all information related to a field and processes it.
|
|
type FieldProcessor interface {
|
|
Process(n *types.Named, f *types.Var, tag string, comment string, parentFields ...string) error
|
|
}
|
|
|
|
// ProcessorConfig lets you configure what processors will be run in given traversal.
|
|
type ProcessorConfig struct {
|
|
Named NamedProcessor
|
|
Field FieldProcessor
|
|
}
|
|
|
|
// NewTraverser returns a new Traverser.
|
|
func NewTraverser(c comments.Comments) *Traverser {
|
|
return &Traverser{
|
|
comments: c,
|
|
}
|
|
}
|
|
|
|
// Traverser goes through all fields of given type recursively. It runs the field
|
|
// processor for every field and named processor for every type it encounters
|
|
// during its depth-first traversal.
|
|
type Traverser struct {
|
|
comments comments.Comments
|
|
}
|
|
|
|
// NOTE(muvaf): We return an error but currently there isn't really anything
|
|
// constructing an error. But we keep that for future type and field processors.
|
|
|
|
// Traverse given type recursively and run given processors.
|
|
func (t *Traverser) Traverse(n *types.Named, cfg *ProcessorConfig, parentFields ...string) error { //nolint:gocognit // This is complex, but pretty easy to read.
|
|
if err := cfg.Named.Process(n, t.comments.For(n.Obj())); err != nil {
|
|
return errors.Wrapf(err, "type processors failed to run for type %s", n.Obj().Name())
|
|
}
|
|
st, ok := n.Underlying().(*types.Struct)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
for i := range st.NumFields() {
|
|
field := st.Field(i)
|
|
tag := st.Tag(i)
|
|
if err := cfg.Field.Process(n, field, tag, t.comments.For(field), parentFields...); err != nil {
|
|
return errors.Wrapf(err, "field processors failed to run for field %s of type %s", field.Name(), n.Obj().Name())
|
|
}
|
|
switch ft := field.Type().(type) {
|
|
case *types.Named:
|
|
if err := t.Traverse(ft, cfg, append(parentFields, field.Name())...); err != nil {
|
|
return errors.Wrapf(err, "failed to traverse type of field %s", field.Name())
|
|
}
|
|
case *types.Pointer:
|
|
if elemType, ok := ft.Elem().(*types.Named); ok {
|
|
if err := t.Traverse(elemType, cfg, append(parentFields, "*"+field.Name())...); err != nil {
|
|
return errors.Wrapf(err, "failed to traverse type of field %s", field.Name())
|
|
}
|
|
}
|
|
case *types.Slice:
|
|
switch elemType := ft.Elem().(type) {
|
|
case *types.Named:
|
|
if err := t.Traverse(elemType, cfg, append(parentFields, "[]"+field.Name())...); err != nil {
|
|
return errors.Wrapf(err, "failed to traverse type of field %s", field.Name())
|
|
}
|
|
case *types.Pointer:
|
|
if elemElemType, ok := elemType.Elem().(*types.Named); ok {
|
|
if err := t.Traverse(elemElemType, cfg, append(parentFields, "[]"+"*"+field.Name())...); err != nil {
|
|
return errors.Wrapf(err, "failed to traverse type of field %s", field.Name())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|