github.com/TheSpiritXIII/controller-tools@v0.14.1/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/TheSpiritXIII/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  	isAsteriskComment := false
    62  	for _, l := range outGroup.List {
    63  		if strings.HasPrefix(l.Text, "/*") {
    64  			isAsteriskComment = true
    65  			break
    66  		}
    67  	}
    68  
    69  	// split lines, and re-join together as a single
    70  	// paragraph, respecting double-newlines as
    71  	// paragraph markers.
    72  	outLines := strings.Split(outGroup.Text(), "\n")
    73  	if outLines[len(outLines)-1] == "" {
    74  		// chop off the extraneous last part
    75  		outLines = outLines[:len(outLines)-1]
    76  	}
    77  
    78  	for i, line := range outLines {
    79  		if isAsteriskComment {
    80  			// Trim any extranous whitespace,
    81  			// for handling /*…*/-style comments,
    82  			// which have whitespace preserved in go/ast:
    83  			line = strings.TrimSpace(line)
    84  		}
    85  
    86  		// Respect that double-newline means
    87  		// actual newline:
    88  		if line == "" {
    89  			outLines[i] = "\n"
    90  		} else {
    91  			outLines[i] = line
    92  		}
    93  	}
    94  	return strings.Join(outLines, "\n")
    95  }
    96  
    97  // PackageMarkers collects all the package-level marker values for the given package.
    98  func PackageMarkers(col *Collector, pkg *loader.Package) (MarkerValues, error) {
    99  	markers, err := col.MarkersInPackage(pkg)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	res := make(MarkerValues)
   104  	for _, file := range pkg.Syntax {
   105  		fileMarkers := markers[file]
   106  		for name, vals := range fileMarkers {
   107  			res[name] = append(res[name], vals...)
   108  		}
   109  	}
   110  
   111  	return res, nil
   112  }
   113  
   114  // FieldInfo contains marker values and commonly used information for a struct field.
   115  type FieldInfo struct {
   116  	// Name is the name of the field (or "" for embedded fields)
   117  	Name string
   118  	// Doc is the Godoc of the field, pre-processed to remove markers and joine
   119  	// single newlines together.
   120  	Doc string
   121  	// Tag struct tag associated with this field (or "" if non existed).
   122  	Tag reflect.StructTag
   123  
   124  	// Markers are all registered markers associated with this field.
   125  	Markers MarkerValues
   126  
   127  	// RawField is the raw, underlying field AST object that this field represents.
   128  	RawField *ast.Field
   129  }
   130  
   131  // TypeInfo contains marker values and commonly used information for a type declaration.
   132  type TypeInfo struct {
   133  	// Name is the name of the type.
   134  	Name string
   135  	// Doc is the Godoc of the type, pre-processed to remove markers and joine
   136  	// single newlines together.
   137  	Doc string
   138  
   139  	// Markers are all registered markers associated with the type.
   140  	Markers MarkerValues
   141  
   142  	// Fields are all the fields associated with the type, if it's a struct.
   143  	// (if not, Fields will be nil).
   144  	Fields []FieldInfo
   145  
   146  	// RawDecl contains the raw GenDecl that the type was declared as part of.
   147  	RawDecl *ast.GenDecl
   148  	// RawSpec contains the raw Spec that declared this type.
   149  	RawSpec *ast.TypeSpec
   150  	// RawFile contains the file in which this type was declared.
   151  	RawFile *ast.File
   152  }
   153  
   154  // TypeCallback is a callback called for each type declaration in a package.
   155  type TypeCallback func(info *TypeInfo)
   156  
   157  // EachType collects all markers, then calls the given callback for each type declaration in a package.
   158  // Each individual spec is considered separate, so
   159  //
   160  //	type (
   161  //	    Foo string
   162  //	    Bar int
   163  //	    Baz struct{}
   164  //	)
   165  //
   166  // yields three calls to the callback.
   167  func EachType(col *Collector, pkg *loader.Package, cb TypeCallback) error {
   168  	markers, err := col.MarkersInPackage(pkg)
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	loader.EachType(pkg, func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec) {
   174  		var fields []FieldInfo
   175  		if structSpec, isStruct := spec.Type.(*ast.StructType); isStruct {
   176  			for _, field := range structSpec.Fields.List {
   177  				for _, name := range field.Names {
   178  					fields = append(fields, FieldInfo{
   179  						Name:     name.Name,
   180  						Doc:      extractDoc(field, nil),
   181  						Tag:      loader.ParseAstTag(field.Tag),
   182  						Markers:  markers[field],
   183  						RawField: field,
   184  					})
   185  				}
   186  				if field.Names == nil {
   187  					fields = append(fields, FieldInfo{
   188  						Doc:      extractDoc(field, nil),
   189  						Tag:      loader.ParseAstTag(field.Tag),
   190  						Markers:  markers[field],
   191  						RawField: field,
   192  					})
   193  				}
   194  			}
   195  		}
   196  
   197  		cb(&TypeInfo{
   198  			Name:    spec.Name.Name,
   199  			Markers: markers[spec],
   200  			Doc:     extractDoc(spec, decl),
   201  			Fields:  fields,
   202  			RawDecl: decl,
   203  			RawSpec: spec,
   204  			RawFile: file,
   205  		})
   206  	})
   207  
   208  	return nil
   209  }