github.com/matthchr/controller-tools@v0.3.1-0.20200602225425-d33ced351ff8/pkg/markers/zip.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package markers
    18  
    19  import (
    20  	"go/ast"
    21  	"go/token"
    22  	"reflect"
    23  	"strings"
    24  
    25  	"github.com/matthchr/controller-tools/pkg/loader"
    26  )
    27  
    28  // extractDoc extracts documentation from the given node, skipping markers
    29  // in the godoc and falling back to the decl if necessary (for single-line decls).
    30  func extractDoc(node ast.Node, decl *ast.GenDecl) string {
    31  	var docs *ast.CommentGroup
    32  	switch docced := node.(type) {
    33  	case *ast.Field:
    34  		docs = docced.Doc
    35  	case *ast.File:
    36  		docs = docced.Doc
    37  	case *ast.GenDecl:
    38  		docs = docced.Doc
    39  	case *ast.TypeSpec:
    40  		docs = docced.Doc
    41  		// type Ident expr expressions get docs attached to the decl,
    42  		// so check for that case (missing Lparen == single line type decl)
    43  		if docs == nil && decl.Lparen == token.NoPos {
    44  			docs = decl.Doc
    45  		}
    46  	}
    47  
    48  	if docs == nil {
    49  		return ""
    50  	}
    51  
    52  	// filter out markers
    53  	var outGroup ast.CommentGroup
    54  	outGroup.List = make([]*ast.Comment, 0, len(docs.List))
    55  	for _, comment := range docs.List {
    56  		if isMarkerComment(comment.Text) {
    57  			continue
    58  		}
    59  		outGroup.List = append(outGroup.List, comment)
    60  	}
    61  
    62  	// split lines, and re-join together as a single
    63  	// paragraph, respecting double-newlines as
    64  	// paragraph markers.
    65  	outLines := strings.Split(outGroup.Text(), "\n")
    66  	if outLines[len(outLines)-1] == "" {
    67  		// chop off the extraneous last part
    68  		outLines = outLines[:len(outLines)-1]
    69  	}
    70  	// respect double-newline meaning actual newline
    71  	for i, line := range outLines {
    72  		if line == "" {
    73  			outLines[i] = "\n"
    74  		}
    75  	}
    76  	return strings.Join(outLines, " ")
    77  }
    78  
    79  // PackageMarkers collects all the package-level marker values for the given package.
    80  func PackageMarkers(col *Collector, pkg *loader.Package) (MarkerValues, error) {
    81  	markers, err := col.MarkersInPackage(pkg)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	res := make(MarkerValues)
    86  	for _, file := range pkg.Syntax {
    87  		fileMarkers := markers[file]
    88  		for name, vals := range fileMarkers {
    89  			res[name] = append(res[name], vals...)
    90  		}
    91  	}
    92  
    93  	return res, nil
    94  }
    95  
    96  // FieldInfo contains marker values and commonly used information for a struct field.
    97  type FieldInfo struct {
    98  	// Name is the name of the field (or "" for embedded fields)
    99  	Name string
   100  	// Doc is the Godoc of the field, pre-processed to remove markers and joine
   101  	// single newlines together.
   102  	Doc string
   103  	// Tag struct tag associated with this field (or "" if non existed).
   104  	Tag reflect.StructTag
   105  
   106  	// Markers are all registered markers associated with this field.
   107  	Markers MarkerValues
   108  
   109  	// RawField is the raw, underlying field AST object that this field represents.
   110  	RawField *ast.Field
   111  }
   112  
   113  // TypeInfo contains marker values and commonly used information for a type declaration.
   114  type TypeInfo struct {
   115  	// Name is the name of the type.
   116  	Name string
   117  	// Doc is the Godoc of the type, pre-processed to remove markers and joine
   118  	// single newlines together.
   119  	Doc string
   120  
   121  	// Markers are all registered markers associated with the type.
   122  	Markers MarkerValues
   123  
   124  	// Fields are all the fields associated with the type, if it's a struct.
   125  	// (if not, Fields will be nil).
   126  	Fields []FieldInfo
   127  
   128  	// RawDecl contains the raw GenDecl that the type was declared as part of.
   129  	RawDecl *ast.GenDecl
   130  	// RawSpec contains the raw Spec that declared this type.
   131  	RawSpec *ast.TypeSpec
   132  	// RawFile contains the file in which this type was declared.
   133  	RawFile *ast.File
   134  }
   135  
   136  // TypeCallback is a callback called for each type declaration in a package.
   137  type TypeCallback func(info *TypeInfo)
   138  
   139  // EachType collects all markers, then calls the given callback for each type declaration in a package.
   140  // Each individual spec is considered separate, so
   141  //
   142  //  type (
   143  //      Foo string
   144  //      Bar int
   145  //      Baz struct{}
   146  //  )
   147  //
   148  // yields three calls to the callback.
   149  func EachType(col *Collector, pkg *loader.Package, cb TypeCallback) error {
   150  	markers, err := col.MarkersInPackage(pkg)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	loader.EachType(pkg, func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec) {
   156  		var fields []FieldInfo
   157  		if structSpec, isStruct := spec.Type.(*ast.StructType); isStruct {
   158  			for _, field := range structSpec.Fields.List {
   159  				for _, name := range field.Names {
   160  					fields = append(fields, FieldInfo{
   161  						Name:     name.Name,
   162  						Doc:      extractDoc(field, nil),
   163  						Tag:      loader.ParseAstTag(field.Tag),
   164  						Markers:  markers[field],
   165  						RawField: field,
   166  					})
   167  				}
   168  				if field.Names == nil {
   169  					fields = append(fields, FieldInfo{
   170  						Doc:      extractDoc(field, nil),
   171  						Tag:      loader.ParseAstTag(field.Tag),
   172  						Markers:  markers[field],
   173  						RawField: field,
   174  					})
   175  				}
   176  			}
   177  		}
   178  
   179  		cb(&TypeInfo{
   180  			Name:    spec.Name.Name,
   181  			Markers: markers[spec],
   182  			Doc:     extractDoc(spec, decl),
   183  			Fields:  fields,
   184  			RawDecl: decl,
   185  			RawSpec: spec,
   186  			RawFile: file,
   187  		})
   188  	})
   189  
   190  	return nil
   191  }