github.com/jamescostian/go-swagger@v0.30.4-0.20221130163922-68364d6b567b/scan/classifier.go (about) 1 //go:build !go1.11 2 // +build !go1.11 3 4 // Copyright 2015 go-swagger maintainers 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package scan 19 20 import ( 21 "fmt" 22 "go/ast" 23 "log" 24 "regexp" 25 26 "golang.org/x/tools/go/loader" 27 ) 28 29 type packageFilter struct { 30 Name string 31 } 32 33 func (pf *packageFilter) Matches(path string) bool { 34 matched, err := regexp.MatchString(pf.Name, path) 35 if err != nil { 36 log.Fatal(err) 37 } 38 return matched 39 } 40 41 type packageFilters []packageFilter 42 43 func (pf packageFilters) HasFilters() bool { 44 return len(pf) > 0 45 } 46 47 func (pf packageFilters) Matches(path string) bool { 48 for _, mod := range pf { 49 if mod.Matches(path) { 50 return true 51 } 52 } 53 return false 54 } 55 56 type classifiedProgram struct { 57 Meta []*ast.File 58 Models []*ast.File 59 Routes []*ast.File 60 Operations []*ast.File 61 Parameters []*ast.File 62 Responses []*ast.File 63 } 64 65 // programClassifier classifies the files of a program into buckets 66 // for processing by a swagger spec generator. This buckets files in 67 // 3 groups: Meta, Models and Operations. 68 // 69 // # Each of these buckets is then processed with an appropriate parsing strategy 70 // 71 // When there are Include or Exclude filters provide they are used to limit the 72 // candidates prior to parsing. 73 // The include filters take precedence over the excludes. So when something appears 74 // in both filters it will be included. 75 type programClassifier struct { 76 Includes packageFilters 77 Excludes packageFilters 78 } 79 80 func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram, error) { 81 var cp classifiedProgram 82 for pkg, pkgInfo := range prog.AllPackages { 83 if Debug { 84 log.Printf("analyzing: %s\n", pkg.Path()) 85 } 86 if pc.Includes.HasFilters() { 87 if !pc.Includes.Matches(pkg.Path()) { 88 continue 89 } 90 } else if pc.Excludes.HasFilters() { 91 if pc.Excludes.Matches(pkg.Path()) { 92 continue 93 } 94 } 95 96 for _, file := range pkgInfo.Files { 97 var ro, op, mt, pm, rs, mm bool // only add a particular file once 98 for _, comments := range file.Comments { 99 var seenStruct string 100 for _, cline := range comments.List { 101 if cline != nil { 102 matches := rxSwaggerAnnotation.FindStringSubmatch(cline.Text) 103 if len(matches) > 1 { 104 switch matches[1] { 105 case "route": 106 if !ro { 107 cp.Routes = append(cp.Routes, file) 108 ro = true 109 } 110 case "operation": 111 if !op { 112 cp.Operations = append(cp.Operations, file) 113 op = true 114 } 115 case "model": 116 if !mm { 117 cp.Models = append(cp.Models, file) 118 mm = true 119 } 120 if seenStruct == "" || seenStruct == matches[1] { 121 seenStruct = matches[1] 122 } else { 123 return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text) 124 } 125 case "meta": 126 if !mt { 127 cp.Meta = append(cp.Meta, file) 128 mt = true 129 } 130 case "parameters": 131 if !pm { 132 cp.Parameters = append(cp.Parameters, file) 133 pm = true 134 } 135 if seenStruct == "" || seenStruct == matches[1] { 136 seenStruct = matches[1] 137 } else { 138 return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text) 139 } 140 case "response": 141 if !rs { 142 cp.Responses = append(cp.Responses, file) 143 rs = true 144 } 145 if seenStruct == "" || seenStruct == matches[1] { 146 seenStruct = matches[1] 147 } else { 148 return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text) 149 } 150 case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type": 151 // TODO: perhaps collect these and pass along to avoid lookups later on 152 case "allOf": 153 case "ignore": 154 default: 155 return nil, fmt.Errorf("classifier: unknown swagger annotation %q", matches[1]) 156 } 157 } 158 159 } 160 } 161 } 162 } 163 } 164 165 return &cp, nil 166 }