1// Copyright 2019 Google Inc. All rights reserved. 2// 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// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bpdoc 16 17import ( 18 "fmt" 19 "go/ast" 20 "go/doc" 21 "go/parser" 22 "go/token" 23 "reflect" 24 "regexp" 25 "runtime" 26 "strings" 27 "sync" 28) 29 30// Handles parsing and low-level processing of Blueprint module source files. Note that most getter 31// functions associated with Reader only fill basic information that can be simply extracted from 32// AST parsing results. More sophisticated processing is performed in bpdoc.go 33type Reader struct { 34 pkgFiles map[string][]string // Map of package name to source files, provided by constructor 35 36 mutex sync.Mutex 37 goPkgs map[string]*doc.Package // Map of package name to parsed Go AST, protected by mutex 38 ps map[string]*PropertyStruct // Map of module type name to property struct, protected by mutex 39} 40 41func NewReader(pkgFiles map[string][]string) *Reader { 42 return &Reader{ 43 pkgFiles: pkgFiles, 44 goPkgs: make(map[string]*doc.Package), 45 ps: make(map[string]*PropertyStruct), 46 } 47} 48 49func (r *Reader) Package(path string) (*Package, error) { 50 goPkg, err := r.goPkg(path) 51 if err != nil { 52 return nil, err 53 } 54 55 return &Package{ 56 Name: goPkg.Name, 57 Path: path, 58 Text: goPkg.Doc, 59 }, nil 60} 61 62func (r *Reader) ModuleType(name string, factory reflect.Value) (*ModuleType, error) { 63 f := runtime.FuncForPC(factory.Pointer()) 64 65 pkgPath, err := funcNameToPkgPath(f.Name()) 66 if err != nil { 67 return nil, err 68 } 69 70 factoryName := strings.TrimPrefix(f.Name(), pkgPath+".") 71 72 text, err := r.getModuleTypeDoc(pkgPath, factoryName) 73 if err != nil { 74 return nil, err 75 } 76 77 return &ModuleType{ 78 Name: name, 79 PkgPath: pkgPath, 80 Text: formatText(text), 81 }, nil 82} 83 84// Return the PropertyStruct associated with a property struct type. The type should be in the 85// format <package path>.<type name> 86func (r *Reader) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) { 87 ps := r.getPropertyStruct(pkgPath, name) 88 89 if ps == nil { 90 pkg, err := r.goPkg(pkgPath) 91 if err != nil { 92 return nil, err 93 } 94 95 for _, t := range pkg.Types { 96 if t.Name == name { 97 ps, err = newPropertyStruct(t) 98 if err != nil { 99 return nil, err 100 } 101 ps = r.putPropertyStruct(pkgPath, name, ps) 102 } 103 } 104 } 105 106 if ps == nil { 107 return nil, fmt.Errorf("package %q type %q not found", pkgPath, name) 108 } 109 110 ps = ps.Clone() 111 ps.SetDefaults(defaults) 112 113 return ps, nil 114} 115 116func (r *Reader) getModuleTypeDoc(pkgPath, factoryFuncName string) (string, error) { 117 goPkg, err := r.goPkg(pkgPath) 118 if err != nil { 119 return "", err 120 } 121 122 for _, fn := range goPkg.Funcs { 123 if fn.Name == factoryFuncName { 124 return fn.Doc, nil 125 } 126 } 127 128 // The doc package may associate the method with the type it returns, so iterate through those too 129 for _, typ := range goPkg.Types { 130 for _, fn := range typ.Funcs { 131 if fn.Name == factoryFuncName { 132 return fn.Doc, nil 133 } 134 } 135 } 136 137 return "", nil 138} 139 140func (r *Reader) getPropertyStruct(pkgPath, name string) *PropertyStruct { 141 r.mutex.Lock() 142 defer r.mutex.Unlock() 143 144 name = pkgPath + "." + name 145 146 return r.ps[name] 147} 148 149func (r *Reader) putPropertyStruct(pkgPath, name string, ps *PropertyStruct) *PropertyStruct { 150 r.mutex.Lock() 151 defer r.mutex.Unlock() 152 153 name = pkgPath + "." + name 154 155 if r.ps[name] != nil { 156 return r.ps[name] 157 } else { 158 r.ps[name] = ps 159 return ps 160 } 161} 162 163// Package AST generation and storage 164func (r *Reader) goPkg(pkgPath string) (*doc.Package, error) { 165 pkg := r.getGoPkg(pkgPath) 166 if pkg == nil { 167 if files, ok := r.pkgFiles[pkgPath]; ok { 168 var err error 169 pkgAST, err := packageAST(files) 170 if err != nil { 171 return nil, err 172 } 173 pkg = doc.New(pkgAST, pkgPath, doc.AllDecls) 174 pkg = r.putGoPkg(pkgPath, pkg) 175 } else { 176 return nil, fmt.Errorf("unknown package %q", pkgPath) 177 } 178 } 179 return pkg, nil 180} 181 182func (r *Reader) getGoPkg(pkgPath string) *doc.Package { 183 r.mutex.Lock() 184 defer r.mutex.Unlock() 185 186 return r.goPkgs[pkgPath] 187} 188 189func (r *Reader) putGoPkg(pkgPath string, pkg *doc.Package) *doc.Package { 190 r.mutex.Lock() 191 defer r.mutex.Unlock() 192 193 if r.goPkgs[pkgPath] != nil { 194 return r.goPkgs[pkgPath] 195 } else { 196 r.goPkgs[pkgPath] = pkg 197 return pkg 198 } 199} 200 201// A regex to find a package path within a function name. It finds the shortest string that is 202// followed by '.' and doesn't have any '/'s left. 203var pkgPathRe = regexp.MustCompile("^(.*?)\\.[^/]+$") 204 205func funcNameToPkgPath(f string) (string, error) { 206 s := pkgPathRe.FindStringSubmatch(f) 207 if len(s) < 2 { 208 return "", fmt.Errorf("failed to extract package path from %q", f) 209 } 210 return s[1], nil 211} 212 213func packageAST(files []string) (*ast.Package, error) { 214 asts := make(map[string]*ast.File) 215 216 fset := token.NewFileSet() 217 for _, file := range files { 218 ast, err := parser.ParseFile(fset, file, nil, parser.ParseComments) 219 if err != nil { 220 return nil, err 221 } 222 asts[file] = ast 223 } 224 225 pkg, _ := ast.NewPackage(fset, asts, nil, nil) 226 return pkg, nil 227} 228