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