github.com/anchore/syft@v1.38.2/internal/jsonschema/comments.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/parser" 7 "go/token" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/invopop/jsonschema" 13 ) 14 15 func copyAliasFieldComments(commentMap map[string]string, repoRoot string) { 16 // find all type aliases by parsing Go source files 17 aliases := findTypeAliases(repoRoot) 18 19 // for each alias, copy field comments from the source type 20 for aliasName, sourceName := range aliases { 21 // find all field comments for the source type 22 for key, comment := range commentMap { 23 // check if this is a field comment for the source type 24 // format: "github.com/anchore/syft/syft/pkg.SourceType.FieldName" 25 if strings.Contains(key, "."+sourceName+".") { 26 // create the corresponding key for the alias 27 aliasKey := strings.Replace(key, "."+sourceName+".", "."+aliasName+".", 1) 28 commentMap[aliasKey] = comment 29 } 30 } 31 } 32 } 33 34 func findTypeAliases(repoRoot string) map[string]string { 35 aliases := make(map[string]string) 36 fset := token.NewFileSet() 37 38 // walk through all Go files in the repo 39 err := filepath.Walk(repoRoot, func(path string, info os.FileInfo, err error) error { 40 if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") { 41 return nil 42 } 43 44 // parse the file 45 file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) 46 if err != nil { 47 return nil 48 } 49 50 // look for type alias declarations 51 ast.Inspect(file, func(n ast.Node) bool { 52 typeSpec, ok := n.(*ast.TypeSpec) 53 if !ok { 54 return true 55 } 56 57 // check if this is a type alias (e.g., type A B where B is an identifier) 58 ident, ok := typeSpec.Type.(*ast.Ident) 59 if !ok { 60 return true 61 } 62 63 // store the alias mapping: aliasName -> sourceName 64 aliases[typeSpec.Name.Name] = ident.Name 65 return true 66 }) 67 68 return nil 69 }) 70 71 if err != nil { 72 fmt.Fprintf(os.Stderr, "error: failed to find type aliases: %v\n", err) 73 panic(err) 74 } 75 76 return aliases 77 } 78 79 func hasDescriptionInAlternatives(schema *jsonschema.Schema) bool { 80 // check oneOf alternatives 81 for _, alt := range schema.OneOf { 82 if alt.Description != "" { 83 return true 84 } 85 } 86 // check anyOf alternatives 87 for _, alt := range schema.AnyOf { 88 if alt.Description != "" { 89 return true 90 } 91 } 92 return false 93 } 94 95 func warnMissingDescriptions(schema *jsonschema.Schema, metadataNames []string) { //nolint:gocognit 96 var missingTypeDescriptions []string 97 var missingFieldDescriptions []string 98 99 // check metadata types for missing descriptions 100 for _, name := range metadataNames { 101 def, ok := schema.Definitions[name] 102 if !ok { 103 continue 104 } 105 106 // check if type has a description 107 if def.Description == "" { 108 missingTypeDescriptions = append(missingTypeDescriptions, name) 109 } 110 111 // check if fields have descriptions 112 if def.Properties != nil { 113 for _, fieldName := range def.Properties.Keys() { 114 fieldSchemaRaw, _ := def.Properties.Get(fieldName) 115 fieldSchema, ok := fieldSchemaRaw.(*jsonschema.Schema) 116 if !ok { 117 continue 118 } 119 120 // skip if field has a description 121 if fieldSchema.Description != "" { 122 continue 123 } 124 125 // skip if field is a reference (descriptions come from the referenced type) 126 if fieldSchema.Ref != "" { 127 continue 128 } 129 130 // skip if field is an array/object with items that are references 131 if fieldSchema.Items != nil && fieldSchema.Items.Ref != "" { 132 continue 133 } 134 135 // skip if field uses oneOf/anyOf with descriptions in the alternatives 136 if hasDescriptionInAlternatives(fieldSchema) { 137 continue 138 } 139 140 missingFieldDescriptions = append(missingFieldDescriptions, fmt.Sprintf("%s.%s", name, fieldName)) 141 } 142 } 143 } 144 145 // report findings 146 if len(missingTypeDescriptions) > 0 { 147 fmt.Fprintf(os.Stderr, "\nwarning: %d metadata types are missing descriptions:\n", len(missingTypeDescriptions)) 148 for _, name := range missingTypeDescriptions { 149 fmt.Fprintf(os.Stderr, " - %s\n", name) 150 } 151 } 152 153 if len(missingFieldDescriptions) > 0 { 154 fmt.Fprintf(os.Stderr, "\nwarning: %d fields are missing descriptions:\n", len(missingFieldDescriptions)) 155 for _, field := range missingFieldDescriptions { 156 fmt.Fprintf(os.Stderr, " - %s\n", field) 157 } 158 } 159 }