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 }