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 }