zip.go 5.6 KB

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