├── testdata ├── tpkg_example_test.go ├── tpkg_test.go ├── tpkg_private.go └── tpkg.go ├── README.md ├── LICENSE ├── srcutil_test.go ├── srcutil.go ├── example_test.go ├── package_test.go └── package.go /testdata/tpkg_example_test.go: -------------------------------------------------------------------------------- 1 | package tpkg_test 2 | 3 | import "fmt" 4 | 5 | func Example() { 6 | fmt.Println("Example") 7 | 8 | // Output: 9 | // Example 10 | } 11 | -------------------------------------------------------------------------------- /testdata/tpkg_test.go: -------------------------------------------------------------------------------- 1 | package tpkg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFuncOne(t *testing.T) {} 9 | 10 | func TestFuncTwo(t *testing.T) {} 11 | 12 | func TestFuncThree(t *testing.T) {} 13 | 14 | func ExamplePublicStruct() { 15 | fmt.Println("ExamplePublicStruct") 16 | 17 | // Output: 18 | // ExamplePublicStruct 19 | } 20 | 21 | func ExamplePublicStruct_MethodOne() { 22 | fmt.Println("ExamplePublicStruct_MethodOne") 23 | 24 | // Output: 25 | // ExamplePublicStruct_MethodOne 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Package: srcutil 2 | 3 | [About](#about) | [Go Doc](https://godoc.org/github.com/cstockton/go-srcutil) 4 | 5 | > Get: 6 | > ```bash 7 | > go get -u github.com/cstockton/go-srcutil 8 | > ``` 9 | > 10 | > Example: 11 | > ```Go 12 | > pkg, err := srcutil.Import("io") 13 | > if err != nil { log.Fatal(err) } 14 | > fmt.Printf("// %s: %s\n", pkg, pkg.Doc) 15 | > 16 | > vars := pkg.Vars() 17 | > for _, v := range vars { 18 | > fmt.Printf("var %v %v\n", v.Name(), v.Type()) 19 | > } 20 | > ``` 21 | > 22 | > Output 23 | > ```Go 24 | > // io: Package io provides basic interfaces to I/O primitives. 25 | > var EOF error 26 | > var ErrClosedPipe error 27 | > var ErrNoProgress error 28 | > var ErrShortBuffer error 29 | > var ErrShortWrite error 30 | > var ErrUnexpectedEOF error 31 | > ``` 32 | 33 | ## About 34 | 35 | Package srcutil provides utilities for working with Go source code. The Go 36 | standard library provides a powerful suite of packages "go/{ast,doc,...}" 37 | which are used by the Go tool chain to compile Go programs. As you initially 38 | try to find your way around you hit a small dependency barrier and have to 39 | learn a small portion of each package. There is a fantastic write up and 40 | collection of examples that I used to learn (or shamelessly copy pasta'd) 41 | while creating this package, currently maintained by: 42 | 43 | ``` 44 | Alan Donovan (https://github.com/golang/example/tree/master/gotypes) 45 | ``` 46 | 47 | In the mean time this package can help you get started with some common use 48 | cases. 49 | 50 | 51 | ## Bugs and Patches 52 | 53 | Feel free to report bugs and submit pull requests. 54 | 55 | * bugs: 56 | 57 | * patches: 58 | 59 | 60 | 61 | 62 | [Go Doc]: https://godoc.org/github.com/cstockton/go-srcutil 63 | -------------------------------------------------------------------------------- /testdata/tpkg_private.go: -------------------------------------------------------------------------------- 1 | package tpkg 2 | 3 | // Pulvinar a habitasse amet illo, iaculis mi condimentum eget id. Consequat 4 | // habitasse erat eros. 5 | const ( 6 | 7 | // constantOne ipsum non lacus mattis. 8 | constantOne = 42 9 | 10 | // ConstantTwo nulla vel tortor hac leo. 11 | constantTwo = true 12 | 13 | // ConstantThree enectus ante orci turpis leo placerat. 14 | constantThree = "Three" 15 | ) 16 | 17 | // Lorem ornare accumsan integer, volutpat luctus sed ante malesuada suscipit 18 | // elementum, scelerisque ut non diam pellentesque hymenaeos. 19 | var ( 20 | 21 | // VariableOne ipsum non lacus mattis. 22 | variableOne = 42 23 | 24 | // VariableTwo nulla vel tortor hac leo. 25 | variableTwo = true 26 | 27 | // VariableThree enectus ante orci turpis leo placerat. 28 | variableThree = "Three" 29 | ) 30 | 31 | // comment for private func. 32 | func funcOne() {} 33 | func funcTwo() string { return `` } 34 | func funcThree(int, string) string { return `` } 35 | 36 | // privateStruct Consectetuer metus blandit est, auctor laoreet, enim leo id 37 | // ante. Suspendisse tincidunt quam ipsum lacinia dui, a ac in enim sed. 38 | type privateStruct struct { 39 | unexportedStr string 40 | unexportedInt int 41 | } 42 | 43 | // comment for privateStruct method. 44 | func (p privateStruct) funcOne() {} 45 | func (p privateStruct) funcTwo() string { return `` } 46 | func (p privateStruct) funcThree(int, string) string { return `` } 47 | func (p *privateStruct) funcOneP() {} 48 | func (p *privateStruct) funcTwoP() string { return `` } 49 | func (p *privateStruct) funcThreeP(int, string) string { return `` } 50 | 51 | // privateStruct Consectetuer metus blandit est, auctor laoreet, enim leo id 52 | // ante. Suspendisse tincidunt quam ipsum lacinia dui, a ac in enim sed. 53 | type privateStructExported struct { 54 | ExportedStr string 55 | ExportedInt int 56 | } 57 | 58 | // comment for privateStruct method. 59 | func (p privateStructExported) FuncOne() {} 60 | func (p privateStructExported) funcTwo() string { return `` } 61 | func (p privateStructExported) funcThree(int, string) string { return `` } 62 | func (p *privateStructExported) FuncOneP() {} 63 | func (p *privateStructExported) funcTwoP() string { return `` } 64 | func (p *privateStructExported) funcThreeP(int, string) string { return `` } 65 | 66 | // HELLO(cstockton): Note hello 1 for testing. 67 | // HELLO(chris): Note hello 2 for testing. 68 | // WORLD(cstockton): Note world 1 for testing. 69 | // WORLD(chris): Note world 2 for testing. 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Chris Stockton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | -------------------------------------------------------------------- 25 | 26 | 27 | Parts of this package include annotation in comments attributing source code to 28 | be in whole, or part from the Go standard library. Permission is granted under 29 | the following license. 30 | 31 | 32 | Copyright (c) 2009 The Go Authors. All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions are 36 | met: 37 | 38 | * Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following disclaimer 42 | in the documentation and/or other materials provided with the 43 | distribution. 44 | * Neither the name of Google Inc. nor the names of its 45 | contributors may be used to endorse or promote products derived from 46 | this software without specific prior written permission. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 49 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 50 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 51 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 52 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 53 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 54 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 55 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 56 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 57 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | -------------------------------------------------------------------------------- /srcutil_test.go: -------------------------------------------------------------------------------- 1 | package srcutil 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path/filepath" 7 | "reflect" 8 | "sort" 9 | "testing" 10 | ) 11 | 12 | func tmust(t *testing.T, err error) { 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | } 17 | 18 | func teq(t *testing.T, exp, got interface{}) { 19 | if !reflect.DeepEqual(exp, got) { 20 | t.Errorf("DeepEqual failed:\n exp: %#v\n got: %#v", exp, got) 21 | } 22 | } 23 | 24 | type testPackage struct { 25 | Name string 26 | Path string 27 | ImportPath string 28 | Names []string 29 | PkgNames []string 30 | PkgTests []string 31 | Types map[string]bool 32 | Funcs map[string]bool 33 | Vars map[string]bool 34 | Consts map[string]bool 35 | DocFuncs map[string]bool 36 | DocVars map[string]bool 37 | DocMethods map[string]bool 38 | } 39 | 40 | func newTestPackage() *testPackage { 41 | cwd, err := os.Getwd() 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | tPkg := &testPackage{ 46 | Name: "tpkg", 47 | Path: filepath.Join(cwd, "testdata"), 48 | ImportPath: "github.com/cstockton/go-srcutil/testdata", 49 | Names: []string{ 50 | "tpkg.go", "tpkg_private.go", "tpkg_test.go"}, 51 | PkgNames: []string{ 52 | "tpkg.go", "tpkg_private.go"}, 53 | PkgTests: []string{ 54 | "tpkg_example_test.go", "tpkg_test.go"}, 55 | Types: map[string]bool{ 56 | "PublicStruct": true, "PublicStructUnexported": true, 57 | "privateStruct": true, "privateStructExported": true}, 58 | Funcs: map[string]bool{ 59 | "funcOne": true, "funcTwo": true, "funcThree": true, "StringFunc": true, 60 | "NiladicFunc": true, "NiladicVoidFunc": true}, 61 | Vars: map[string]bool{ 62 | "variableOne": true, "variableTwo": true, "variableThree": true, 63 | "VariableOne": true, "VariableTwo": true, "VariableThree": true}, 64 | Consts: map[string]bool{ 65 | "constantOne": true, "constantTwo": true, "constantThree": true, 66 | "ConstantOne": true, "ConstantTwo": true, "ConstantThree": true}, 67 | } 68 | return tPkg 69 | } 70 | 71 | func (p *testPackage) paths(s []string) []string { 72 | paths := make([]string, len(s)) 73 | for i, name := range s { 74 | paths[i] = filepath.Join(p.Path, name) 75 | } 76 | sort.Strings(paths) 77 | return paths 78 | } 79 | 80 | var tPkg = newTestPackage() 81 | 82 | func TestContext(t *testing.T) { 83 | cwd, err := os.Getwd() 84 | tmust(t, err) 85 | 86 | t.Run("Creation", func(t *testing.T) { 87 | t.Run("FromWorkDir", func(t *testing.T) { 88 | ctx := FromWorkDir() 89 | teq(t, cwd, ctx.SourceDir) 90 | }) 91 | t.Run("FromStandard", func(t *testing.T) { 92 | ctx := FromStandard() 93 | teq(t, ``, ctx.GOPATH) 94 | teq(t, ctx.GOROOT+"/src", ctx.SourceDir) 95 | }) 96 | t.Run("FromDir", func(t *testing.T) { 97 | ctx := FromDir(`.`) 98 | teq(t, `.`, ctx.SourceDir) 99 | }) 100 | }) 101 | } 102 | 103 | func TestImport(t *testing.T) { 104 | ctx := FromWorkDir() 105 | 106 | t.Run("FromGOPATH", func(t *testing.T) { 107 | pkg, err := ctx.Import(tPkg.ImportPath) 108 | tmust(t, err) 109 | teq(t, tPkg.Name, pkg.Name) 110 | }) 111 | t.Run("FromStandardLibrary", func(t *testing.T) { 112 | pkg, err := ctx.Import("reflect") 113 | tmust(t, err) 114 | teq(t, "reflect", pkg.Name) 115 | }) 116 | t.Run("Failure", func(t *testing.T) { 117 | pkg, err := ctx.Import("thislibrarydoesntexist") 118 | if err == nil { 119 | t.Errorf("expected error for non-existent import") 120 | } 121 | if pkg != nil { 122 | t.Errorf("expected nil pkg for non-existent import") 123 | } 124 | }) 125 | } 126 | -------------------------------------------------------------------------------- /srcutil.go: -------------------------------------------------------------------------------- 1 | // Package srcutil provides utilities for working with Go source code. The Go 2 | // standard library provides a powerful suite of packages "go/{ast,doc,...}" 3 | // which are used by the Go tool chain to compile Go programs. As you initially 4 | // try to find your way around you hit a small dependency barrier and have to 5 | // learn a small portion of each package. There is a fantastic write up and 6 | // collection of examples that I used to learn (or shamelessly copy pasta'd) 7 | // while creating this package, currently maintained by: 8 | // 9 | // Alan Donovan (https://github.com/golang/example/tree/master/gotypes) 10 | // 11 | // In the mean time this package can help you get started with some common use 12 | // cases. 13 | package srcutil 14 | 15 | import ( 16 | "fmt" 17 | "go/build" 18 | "go/parser" 19 | "os" 20 | "path/filepath" 21 | ) 22 | 23 | var ( 24 | 25 | // DefaultContext is a Context configured as if you were in the directory of 26 | // the source code calling this library and working normally. It uses the 27 | // currently configured GOROOT, GOPATH and the source dir is set to your 28 | // current working directory. 29 | DefaultContext = FromWorkDir() 30 | 31 | // DefaultParseMode adds ParseComments to the default parser.Mode used in the 32 | // go/parser package. 33 | DefaultParseMode = parser.ParseComments 34 | 35 | // DefaultImportMode is the same as the default build.ImportMode used in the 36 | // go/build package. 37 | DefaultImportMode = build.ImportMode(0) 38 | ) 39 | 40 | // FromDir returns a Context configured with the SourceDir set to the given dir. 41 | // It uses the default build.ImportMode and build.Default for build.Context and 42 | // your GOROOT and GOPATH from the environment to determine the location of 43 | // packages and the standard library. 44 | func FromDir(fromDir string) *Context { 45 | return &Context{Context: build.Default, SourceDir: fromDir} 46 | } 47 | 48 | // FromStandard returns a Context configured to only contain the Go standard 49 | // library, to do this it simply excludes your GOPATH and sets the SourceDir 50 | // to the GOROOT/src. 51 | func FromStandard() *Context { 52 | ctx := FromDir(``) 53 | ctx.GOPATH = `` 54 | ctx.SourceDir = filepath.Join(ctx.GOROOT, "src") 55 | return ctx 56 | } 57 | 58 | // FromWorkDir is like FromDir except it sets the Source dir to your working 59 | // directory. It is used as the DefaultContext. 60 | func FromWorkDir() *Context { 61 | return FromDir(defaultToGetwd()) 62 | } 63 | 64 | // Context is not something you need to interact with for common use cases, 65 | // instead calling the top level functions that return Package types directly 66 | // which will use the DefaultContext. 67 | // 68 | // This structure is like build.Context except it includes the ImportMode and a 69 | // SourceDir that will default to the current working directory. It sits at the 70 | // top of this packages dependency hierarchy as it loads the top level useful 71 | // object, Package. 72 | type Context struct { 73 | build.Context 74 | 75 | // SourceDir defines where the code lives relative for operations you perform. 76 | // For example if you call Context.Import("reflect") it will attempt to import 77 | // that as if the calling package was SourceDir. So if a vendor/ directory 78 | // existing within SourceDir with a reflect package that would be imported 79 | // instead. 80 | SourceDir string 81 | } 82 | 83 | // String implements fmt.Stringer. 84 | func (c *Context) String() string { 85 | return fmt.Sprintf("Context(%s -> %s)", c.SourceDir, c.GOROOT) 86 | } 87 | 88 | func defaultToGetwd(srcDirs ...string) string { 89 | for _, srcDir := range srcDirs { 90 | if len(srcDir) > 0 { 91 | return srcDir 92 | } 93 | } 94 | dir, err := os.Getwd() 95 | if err != nil { 96 | dir = "." 97 | } 98 | return dir 99 | } 100 | 101 | // Import will behave just like using a import declaration from code residing 102 | // within the SourceDir of this Context. If you did not explicitly set the 103 | // SourceDir it will use your current working directory. 104 | func (c *Context) Import(pkgName string) (*Package, error) { 105 | buildPkg, err := c.Context.Import(pkgName, defaultToGetwd(c.SourceDir), DefaultImportMode) 106 | if err != nil { 107 | return nil, err 108 | } 109 | return &Package{Package: *buildPkg}, nil 110 | } 111 | -------------------------------------------------------------------------------- /testdata/tpkg.go: -------------------------------------------------------------------------------- 1 | // Package tpkg is used for testing srcutil. Lorem ipsum dolor sit amet, 2 | // consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et 3 | // dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 4 | // ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure 5 | // dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 6 | // pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 7 | // officia deserunt mollit anim id est laborum. 8 | package tpkg 9 | 10 | // init gravida malesuada, turpis lacus feugiat quis et diam. Dolor nisl fusce. 11 | func init() {} 12 | 13 | // NiladicFunc occaecati accumsan, metus magna sollicitudin, morbi mauris et 14 | // eos quis placerat suspendisse quis, est laoreet sunt vestibulum pharetra 15 | // turpis, etiam ad vestibulum nonummy mus viverra. 16 | func NiladicFunc() string { return `` } 17 | 18 | // NiladicVoidFunc occaecati accumsan, metus magna sollicitudin, morbi mauris 19 | // et eos quis placerat suspendisse quis, est laoreet sunt vestibulum pharetra 20 | // turpis, etiam ad vestibulum nonummy mus viverra. 21 | func NiladicVoidFunc() {} 22 | 23 | // StringFunc occaecati accumsan, metus magna sollicitudin, morbi mauris et 24 | // eos quis placerat suspendisse quis, est laoreet sunt vestibulum pharetra 25 | // turpis, etiam ad vestibulum nonummy mus viverra. 26 | func StringFunc(str string) string { return `` } 27 | 28 | // Pulvinar a habitasse amet illo, iaculis mi condimentum eget id. Consequat 29 | // habitasse erat eros. 30 | const ( 31 | 32 | // ConstantOne ipsum non lacus mattis. 33 | ConstantOne = 42 34 | 35 | // ConstantTwo nulla vel tortor hac leo. 36 | ConstantTwo = true 37 | 38 | // ConstantThree enectus ante orci turpis leo placerat. 39 | ConstantThree = "Three" 40 | ) 41 | 42 | // Lorem ornare accumsan integer, volutpat luctus sed ante malesuada suscipit 43 | // elementum, scelerisque ut non diam pellentesque hymenaeos. 44 | var ( 45 | 46 | // VariableOne ipsum non lacus mattis. 47 | VariableOne = 42 48 | 49 | // VariableTwo nulla vel tortor hac leo. 50 | VariableTwo = true 51 | 52 | // VariableThree enectus ante orci turpis leo placerat. 53 | VariableThree = "Three" 54 | ) 55 | 56 | // PublicStruct proin libero arcu, rerum orci tincidunt, lacus tempor sapien 57 | // platea ullamcorper. Nullam velit, ipsum erat varius nam diam arcu vestibulum. 58 | type PublicStruct struct { 59 | Name string `tagOne:"structtag1" tagTwo:"structtag2"` 60 | Number int 61 | } 62 | 63 | // MethodOne rutrum convallis lorem lacus, eu fusce mi sapien vitae. 64 | func (p PublicStruct) MethodOne() {} 65 | 66 | // MethodTwo rutrum convallis lorem lacus, eu fusce mi sapien vitae. 67 | func (p PublicStruct) MethodTwo() string { return `` } 68 | 69 | // MethodThree rutrum convallis lorem lacus, eu fusce mi sapien vitae. 70 | func (p PublicStruct) MethodThree(int, string) string { return `` } 71 | 72 | // MethodOneP rutrum convallis lorem lacus, eu fusce mi sapien vitae. 73 | func (p *PublicStruct) MethodOneP() {} 74 | 75 | // MethodTwoP rutrum convallis lorem lacus, eu fusce mi sapien vitae. 76 | func (p *PublicStruct) MethodTwoP() string { return `` } 77 | 78 | // MethodThreeP rutrum convallis lorem lacus, eu fusce mi sapien vitae. 79 | func (p *PublicStruct) MethodThreeP(int, string) string { return `` } 80 | 81 | // PublicStructUnexported proin libero arcu, rerum orci tincidunt, lacus tempor 82 | // sapien platea ullamcorper. Nullam velit, ipsum erat varius nam diam arcu 83 | // vestibulum. 84 | type PublicStructUnexported struct { 85 | name string 86 | number int 87 | } 88 | 89 | // MethodOne rutrum convallis lorem lacus, eu fusce mi sapien vitae. 90 | func (p PublicStructUnexported) MethodOne() {} 91 | 92 | // MethodTwo rutrum convallis lorem lacus, eu fusce mi sapien vitae. 93 | func (p PublicStructUnexported) MethodTwo() string { return `` } 94 | 95 | // MethodThree rutrum convallis lorem lacus, eu fusce mi sapien vitae. 96 | func (p PublicStructUnexported) MethodThree(int, string) string { return `` } 97 | 98 | // comment for private method. 99 | func (p PublicStructUnexported) methodOne() {} 100 | func (p PublicStructUnexported) methodTwo() string { return `` } 101 | func (p PublicStructUnexported) methodThree(int, string) string { return `` } 102 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package srcutil_test 2 | 3 | import ( 4 | "fmt" 5 | "go/doc" 6 | "log" 7 | "strings" 8 | 9 | "github.com/cstockton/go-srcutil" 10 | ) 11 | 12 | func Example() { 13 | pkg, err := srcutil.Import("io") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | fmt.Printf("// %s: %s\n", pkg, pkg.Doc) 18 | 19 | vars := pkg.Vars() 20 | for _, v := range vars { 21 | fmt.Printf("var %v %v\n", v.Name(), v.Type()) 22 | } 23 | 24 | // Output: 25 | // // io: Package io provides basic interfaces to I/O primitives. 26 | // var EOF error 27 | // var ErrClosedPipe error 28 | // var ErrNoProgress error 29 | // var ErrShortBuffer error 30 | // var ErrShortWrite error 31 | // var ErrUnexpectedEOF error 32 | } 33 | 34 | func ExamplePackage_Docs() { 35 | pkg, err := srcutil.Import("io") 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | docs := pkg.Docs() 40 | 41 | consts := docs.Consts() 42 | fmt.Printf("// %v", consts[0].Doc) 43 | fmt.Printf("const(\n %v\n)\n\n", strings.Join(consts[0].Names, "\n ")) 44 | 45 | vars := docs.Vars() 46 | for _, v := range vars { 47 | fmt.Printf("// %v", doc.Synopsis(consts[0].Doc)) 48 | fmt.Printf("var %v\n", v.Names[0]) 49 | } 50 | fmt.Print("\n") 51 | 52 | types := docs.Types() 53 | for _, typ := range types { 54 | if strings.Contains(typ.Name, "Reader") { 55 | fmt.Printf("// %v\n", doc.Synopsis(typ.Doc)) 56 | for _, f := range typ.Funcs { 57 | fmt.Printf("// %v\n", doc.Synopsis(f.Doc)) 58 | } 59 | } 60 | } 61 | 62 | // Output: 63 | // // Seek whence values. 64 | // const( 65 | // SeekStart 66 | // SeekCurrent 67 | // SeekEnd 68 | // ) 69 | // 70 | // // Seek whence values.var EOF 71 | // // Seek whence values.var ErrClosedPipe 72 | // // Seek whence values.var ErrNoProgress 73 | // // Seek whence values.var ErrShortBuffer 74 | // // Seek whence values.var ErrShortWrite 75 | // // Seek whence values.var ErrUnexpectedEOF 76 | // 77 | // // ByteReader is the interface that wraps the ReadByte method. 78 | // // A LimitedReader reads from R but limits the amount of data returned to just N bytes. 79 | // // A PipeReader is the read half of a pipe. 80 | // // Pipe creates a synchronous in-memory pipe. 81 | // // Reader is the interface that wraps the basic Read method. 82 | // // LimitReader returns a Reader that reads from r but stops with EOF after n bytes. 83 | // // MultiReader returns a Reader that's the logical concatenation of the provided input readers. 84 | // // TeeReader returns a Reader that writes to w what it reads from r. 85 | // // ReaderAt is the interface that wraps the basic ReadAt method. 86 | // // ReaderFrom is the interface that wraps the ReadFrom method. 87 | // // RuneReader is the interface that wraps the ReadRune method. 88 | // // SectionReader implements Read, Seek, and ReadAt on a section of an underlying ReaderAt. 89 | // // NewSectionReader returns a SectionReader that reads from r starting at offset off and stops with EOF after n bytes. 90 | } 91 | 92 | func ExamplePackage_Methods() { 93 | pkg, err := srcutil.Import("bufio") 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | pkgMethods := pkg.Methods() 98 | 99 | printer := func(methodSet srcutil.MethodSet) { 100 | fmt.Printf("type %v (%d methods)\n", methodSet.Name, methodSet.Len()) 101 | for _, name := range methodSet.Names() { 102 | method := methodSet.Methods[name] 103 | fmt.Printf(" %v%v\n returns %v\n", name, method.Params(), method.Results()) 104 | } 105 | } 106 | printer(pkgMethods["Reader"]) 107 | 108 | // Output: 109 | // type Reader (18 methods) 110 | // Buffered() 111 | // returns (int) 112 | // Discard(n int) 113 | // returns (discarded int, err error) 114 | // Peek(n int) 115 | // returns ([]byte, error) 116 | // Read(p []byte) 117 | // returns (n int, err error) 118 | // ReadByte() 119 | // returns (byte, error) 120 | // ReadBytes(delim byte) 121 | // returns ([]byte, error) 122 | // ReadLine() 123 | // returns (line []byte, isPrefix bool, err error) 124 | // ReadRune() 125 | // returns (r rune, size int, err error) 126 | // ReadSlice(delim byte) 127 | // returns (line []byte, err error) 128 | // ReadString(delim byte) 129 | // returns (string, error) 130 | // Reset(r io.Reader) 131 | // returns () 132 | // UnreadByte() 133 | // returns (error) 134 | // UnreadRune() 135 | // returns (error) 136 | // WriteTo(w io.Writer) 137 | // returns (n int64, err error) 138 | // fill() 139 | // returns () 140 | // readErr() 141 | // returns (error) 142 | // reset(buf []byte, r io.Reader) 143 | // returns () 144 | // writeBuf(w io.Writer) 145 | // returns (int64, error) 146 | } 147 | -------------------------------------------------------------------------------- /package_test.go: -------------------------------------------------------------------------------- 1 | package srcutil 2 | 3 | import ( 4 | "fmt" 5 | "go/build" 6 | "testing" 7 | ) 8 | 9 | func TestPackage(t *testing.T) { 10 | ctx := FromWorkDir() 11 | 12 | t.Run("init", func(t *testing.T) { 13 | buildPkg, err := build.Default.Import(tPkg.ImportPath, defaultToGetwd(), 0) 14 | tmust(t, err) 15 | pkg := Package{Package: *buildPkg} 16 | if pkg.tc != nil { 17 | t.Errorf("expected nil tc for uninitialized Package") 18 | } 19 | pkg.Synopsis() 20 | exp := fmt.Sprintf("%p", pkg.tc) 21 | if pkg.tc == nil { 22 | t.Errorf("expected non-nil tc for initialized Package") 23 | } 24 | t.Run("Once", func(t *testing.T) { 25 | pkg.Synopsis() 26 | got := fmt.Sprintf("%p", pkg.tc) 27 | teq(t, exp, got) 28 | }) 29 | }) 30 | t.Run("Parsing", func(t *testing.T) { 31 | t.Run("ToAst", func(t *testing.T) { 32 | pkg, err := ctx.Import(tPkg.ImportPath) 33 | tmust(t, err) 34 | fileSet, astPkg, err := pkg.ToAst() 35 | tmust(t, err) 36 | teq(t, true, fileSet.Base() > 0) 37 | teq(t, tPkg.Name, astPkg.Name) 38 | 39 | t.Run("Failure", func(t *testing.T) { 40 | pkg.Package.Dir = `` 41 | fileSet, astPkg, err := pkg.ToAst() 42 | if err == nil { 43 | t.Errorf("expected error for non-existent import") 44 | } 45 | if fileSet != nil { 46 | t.Errorf("expected nil fileSet for non-existent import") 47 | } 48 | if astPkg != nil { 49 | t.Errorf("expected nil astPkg for non-existent import") 50 | } 51 | }) 52 | }) 53 | }) 54 | t.Run("ToTypesInfo", func(t *testing.T) { 55 | pkg, err := ctx.Import(tPkg.ImportPath) 56 | tmust(t, err) 57 | typesInfo, typesPkg, err := pkg.ToInfo() 58 | tmust(t, err) 59 | teq(t, tPkg.Name, typesPkg.Name()) 60 | teq(t, true, len(typesInfo.Types) > 0) 61 | 62 | t.Run("Failure", func(t *testing.T) { 63 | pkg.Package.Dir = `` 64 | typesInfo, typesPkg, err := pkg.ToInfo() 65 | if err == nil { 66 | t.Errorf("expected error for non-existent import") 67 | } 68 | if typesPkg != nil { 69 | t.Errorf("expected nil typesPkg for non-existent import") 70 | } 71 | if typesInfo != nil { 72 | t.Errorf("expected nil typesInfo for non-existent import") 73 | } 74 | }) 75 | }) 76 | t.Run("ToTypes", func(t *testing.T) { 77 | pkg, err := ctx.Import(tPkg.ImportPath) 78 | tmust(t, err) 79 | typesPkg, err := pkg.ToTypes() 80 | tmust(t, err) 81 | teq(t, tPkg.Name, typesPkg.Name()) 82 | 83 | t.Run("Failure", func(t *testing.T) { 84 | pkg.Package.Dir = `` 85 | typesPkg, err := pkg.ToTypes() 86 | if err == nil { 87 | t.Errorf("expected error for non-existent import") 88 | } 89 | if typesPkg != nil { 90 | t.Errorf("expected nil typesPkg for non-existent import") 91 | } 92 | }) 93 | }) 94 | t.Run("ToDoc", func(t *testing.T) { 95 | pkg, err := ctx.Import(tPkg.ImportPath) 96 | tmust(t, err) 97 | docPkg, err := pkg.ToDoc() 98 | tmust(t, err) 99 | teq(t, tPkg.Name, docPkg.Name) 100 | 101 | t.Run("Failure", func(t *testing.T) { 102 | pkg.Package.Dir = `` 103 | docPkg, err := pkg.ToDoc() 104 | if err == nil { 105 | t.Errorf("expected error for non-existent import") 106 | } 107 | if docPkg != nil { 108 | t.Errorf("expected nil docPkg for non-existent import") 109 | } 110 | }) 111 | }) 112 | } 113 | 114 | const ( 115 | TestConstant = "I am for testing." 116 | ) 117 | 118 | func TestDocMethods(t *testing.T) { 119 | ctx := FromWorkDir() 120 | pkg, err := ctx.Import(tPkg.ImportPath) 121 | tmust(t, err) 122 | docs := pkg.Docs() 123 | 124 | t.Run("Notes", func(t *testing.T) { 125 | notes := docs.Notes() 126 | got, ok := notes["HELLO"] 127 | teq(t, true, ok) 128 | teq(t, 2, len(got)) 129 | teq(t, "Note hello 1 for testing.\n", got[0].Body) 130 | teq(t, "Note hello 2 for testing.\n", got[1].Body) 131 | 132 | got, ok = notes["WORLD"] 133 | teq(t, true, ok) 134 | teq(t, 2, len(got)) 135 | teq(t, "Note world 1 for testing.\n", got[0].Body) 136 | teq(t, "Note world 2 for testing.\n", got[1].Body) 137 | }) 138 | t.Run("Examples", func(t *testing.T) { 139 | got := docs.Examples() 140 | teq(t, true, len(got) > 0) 141 | teq(t, "PublicStruct", got[0].Name) 142 | }) 143 | t.Run("Consts", func(t *testing.T) { 144 | exp := "ConstantOne" 145 | got := docs.Consts() 146 | teq(t, true, len(got) > 0) 147 | teq(t, exp, got[0].Names[0]) 148 | }) 149 | t.Run("Types", func(t *testing.T) { 150 | types := docs.Types() 151 | teq(t, true, len(types) > 0) 152 | }) 153 | t.Run("Methods", func(t *testing.T) { 154 | methods := docs.Methods() 155 | teq(t, true, len(methods) > 0) 156 | }) 157 | t.Run("Vars", func(t *testing.T) { 158 | vars := docs.Vars() 159 | teq(t, true, len(vars) > 0) 160 | }) 161 | t.Run("Funcs", func(t *testing.T) { 162 | funcs := docs.Funcs() 163 | teq(t, true, len(funcs) > 0) 164 | }) 165 | } 166 | 167 | func TestFiles(t *testing.T) { 168 | ctx := FromWorkDir() 169 | pkg, err := ctx.Import(tPkg.ImportPath) 170 | tmust(t, err) 171 | files := pkg.Files() 172 | teq(t, pkg, files.Package) 173 | 174 | t.Run("Names", func(t *testing.T) { 175 | got := files.Names() 176 | teq(t, tPkg.Names, got) 177 | }) 178 | t.Run("Paths", func(t *testing.T) { 179 | got := files.Paths() 180 | teq(t, tPkg.paths(tPkg.Names), got) 181 | }) 182 | t.Run("SourcePaths", func(t *testing.T) { 183 | got := files.SourcePaths() 184 | teq(t, tPkg.paths(tPkg.PkgNames), got) 185 | }) 186 | t.Run("TestPaths", func(t *testing.T) { 187 | got := files.TestPaths() 188 | teq(t, tPkg.paths(tPkg.PkgTests), got) 189 | }) 190 | } 191 | 192 | func TestFuncs(t *testing.T) { 193 | ctx := FromWorkDir() 194 | pkg, err := ctx.Import(tPkg.ImportPath) 195 | tmust(t, err) 196 | 197 | t.Run("Funcs", func(t *testing.T) { 198 | ms, err := pkg.MethodSet("PublicStruct") 199 | tmust(t, err) 200 | teq(t, ms.Name, "PublicStruct") 201 | m, ok := ms.Methods["MethodOne"] 202 | teq(t, true, ok) 203 | teq(t, "MethodOne", m.Name()) 204 | }) 205 | } 206 | 207 | func TestMethods(t *testing.T) { 208 | ctx := FromWorkDir() 209 | pkg, err := ctx.Import(tPkg.ImportPath) 210 | tmust(t, err) 211 | 212 | t.Run("Methods", func(t *testing.T) { 213 | methods := pkg.Methods() 214 | teq(t, 2, len(methods)) 215 | _, ok := methods["PublicStruct"] 216 | teq(t, true, ok) 217 | }) 218 | t.Run("MethodSet", func(t *testing.T) { 219 | ms, err := pkg.MethodSet("PublicStruct") 220 | tmust(t, err) 221 | teq(t, "PublicStruct", ms.Name) 222 | m, ok := ms.Methods["MethodOne"] 223 | teq(t, true, ok) 224 | teq(t, "MethodOne", m.Name()) 225 | 226 | t.Run("Names", func(t *testing.T) { 227 | teq(t, []string{"MethodOne", "MethodOneP", "MethodThree", "MethodThreeP", 228 | "MethodTwo", "MethodTwoP"}, ms.Names()) 229 | }) 230 | }) 231 | } 232 | 233 | func TestStructs(t *testing.T) { 234 | ctx := FromWorkDir() 235 | pkg, err := ctx.Import(tPkg.ImportPath) 236 | tmust(t, err) 237 | 238 | t.Run("Structs", func(t *testing.T) { 239 | structs := pkg.Structs() 240 | teq(t, 2, len(structs)) 241 | var ( 242 | found bool 243 | ps Struct 244 | ) 245 | for _, s := range structs { 246 | if s.Named.String() == `tpkg.PublicStruct` { 247 | ps, found = s, true 248 | } 249 | } 250 | if !found { 251 | t.Fatal(`did not find struct tpkg.PublicStruct`) 252 | } 253 | exp := `tagOne:"structtag1" tagTwo:"structtag2"` 254 | if tag := ps.Tag(0); tag != exp { 255 | t.Fatalf(`exp %v; got %v`, exp, tag) 256 | } 257 | }) 258 | } 259 | -------------------------------------------------------------------------------- /package.go: -------------------------------------------------------------------------------- 1 | package srcutil 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/build" 7 | "go/doc" 8 | "go/importer" 9 | "go/parser" 10 | "go/token" 11 | "go/types" 12 | "path/filepath" 13 | "sort" 14 | "strings" 15 | "sync" 16 | "unicode" 17 | "unicode/utf8" 18 | ) 19 | 20 | // A Package here has the same meaning as in Go. It embeds a build.Package and 21 | // provides methods to centralize some of the common operations for working with 22 | // Go source code and makes it easy to create some of the lower level compiler 23 | // toolchain primitives like ast, types and parser packages. It should be safe 24 | // for concurrent use from multiple Goroutines. 25 | // 26 | // You should not create Package values with composite literals, instead use one 27 | // of the functions in this package so it may be initialized safely. 28 | type Package struct { 29 | build.Package 30 | once sync.Once 31 | tc *toolchain 32 | } 33 | 34 | // Import is shorthand for FromWorkDir().Import("pkgname"). 35 | func Import(pkgName string) (*Package, error) { 36 | pkg, err := FromWorkDir().Import(pkgName) 37 | if err != nil { 38 | return nil, err 39 | } 40 | if err = pkg.init(); err != nil { 41 | return nil, err 42 | } 43 | return pkg, nil 44 | } 45 | 46 | type toolchain struct { 47 | fileSet *token.FileSet 48 | astPkg *ast.Package 49 | docPkg *doc.Package 50 | typesPkg *types.Package 51 | typesInfo *types.Info 52 | } 53 | 54 | // Synopsis implements fmt.Stringer. 55 | func (p *Package) Synopsis() string { 56 | p.init() 57 | return doc.Synopsis(p.tc.docPkg.Doc) 58 | } 59 | 60 | // String implements fmt.Stringer. 61 | func (p *Package) String() string { 62 | p.init() 63 | return fmt.Sprintf("%s", p.Name) 64 | } 65 | 66 | // ToAst provides access to an associated pair of *token.FileSet and 67 | // ast.Package. A new pair is created each call and a nil pointer will be 68 | // returned when error is non-nil. 69 | func (p *Package) ToAst() (*token.FileSet, *ast.Package, error) { 70 | tc, err := p.toToolchain(nil) 71 | if err != nil { 72 | return nil, nil, err 73 | } 74 | return tc.fileSet, tc.astPkg, nil 75 | } 76 | 77 | // ToDoc provides access to a *doc.Package. A new *doc.Package will be created 78 | // each call and a nil pointer will be returned when error is non-nil. 79 | func (p *Package) ToDoc() (*doc.Package, error) { 80 | tc, err := p.toToolchain(nil) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return tc.docPkg, nil 85 | } 86 | 87 | func (p *Package) typesInfo() *types.Info { 88 | info := &types.Info{ 89 | Types: make(map[ast.Expr]types.TypeAndValue), 90 | Defs: make(map[*ast.Ident]types.Object), 91 | Uses: make(map[*ast.Ident]types.Object), 92 | Implicits: make(map[ast.Node]types.Object), 93 | Selections: make(map[*ast.SelectorExpr]*types.Selection), 94 | Scopes: make(map[ast.Node]*types.Scope), 95 | } 96 | return info 97 | } 98 | 99 | // ToTypes provides access to a *types.Package. A new *types.Package will be 100 | // created each call and a nil pointer will be returned when error is non-nil. 101 | func (p *Package) ToTypes() (*types.Package, error) { 102 | tc, err := p.toToolchain(nil) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return tc.typesPkg, nil 107 | } 108 | 109 | // ToInfo is like ToTypes but also returns a *types.Info that contains all the 110 | // Info maps declared and ready to query. 111 | func (p *Package) ToInfo() (*types.Info, *types.Package, error) { 112 | tc, err := p.toToolchain(p.typesInfo()) 113 | if err != nil { 114 | return nil, nil, err 115 | } 116 | return tc.typesInfo, tc.typesPkg, nil 117 | } 118 | 119 | // Docs groups the documentation related methods. 120 | type Docs struct { 121 | Package *Package 122 | } 123 | 124 | // Docs returns a Docs struct to perform common operations related to 125 | // documentation using the go/doc 126 | func (p *Package) Docs() Docs { 127 | p.init() 128 | return Docs{p} 129 | } 130 | 131 | func (d *Docs) indirectValues(s []*doc.Value) []doc.Value { 132 | out := make([]doc.Value, len(s)) 133 | for i := range s { 134 | out[i] = *s[i] 135 | } 136 | return out 137 | } 138 | 139 | // Examples returns a slice of doc.Example for each declared Go example. 140 | func (d *Docs) Examples() []doc.Example { 141 | astFiles := d.Package.astFiles(d.Package.tc.astPkg) 142 | s := doc.Examples(astFiles...) 143 | out := make([]doc.Example, len(s)) 144 | for i := range s { 145 | out[i] = *s[i] 146 | } 147 | return out 148 | } 149 | 150 | // Notes returns all marked comments starting with "MARKER(uid): note body." 151 | // as described in the go/doc package. I.E.: 152 | // // TODO(cstockton): Fix this. 153 | // // BUG(cstockton): Broken. 154 | func (d *Docs) Notes() map[string][]doc.Note { 155 | m := d.Package.tc.docPkg.Notes 156 | out := make(map[string][]doc.Note) 157 | for k, ns := range m { 158 | out[k] = make([]doc.Note, len(ns)) 159 | for i := range ns { 160 | out[k][i] = *ns[i] 161 | } 162 | } 163 | return out 164 | } 165 | 166 | // Consts returns declared constants in the go/doc package style, which 167 | // groups by the entire const ( Const1 = 1, Const2 = .. ) blocks. 168 | func (d *Docs) Consts() []doc.Value { 169 | return d.indirectValues(d.Package.tc.docPkg.Consts) 170 | } 171 | 172 | // Types returns a slice of doc.Type representing exported functions. 173 | func (d *Docs) Types() []doc.Type { 174 | s := d.Package.tc.docPkg.Types 175 | out := make([]doc.Type, len(s)) 176 | for i := range s { 177 | out[i] = *s[i] 178 | } 179 | return out 180 | } 181 | 182 | // Methods returns declared methods of doc.Func types grouped in a map of 183 | // string type names. 184 | func (d *Docs) Methods() map[string][]doc.Func { 185 | types := d.Types() 186 | out := make(map[string][]doc.Func) 187 | for _, typ := range types { 188 | out[typ.Name] = make([]doc.Func, len(typ.Funcs)) 189 | for i := range typ.Funcs { 190 | out[typ.Name][i] = *typ.Funcs[i] 191 | } 192 | } 193 | return out 194 | } 195 | 196 | // Vars returns declared variables in the go/doc package style, which groups 197 | // the by the var ( Var1 = 1, Var2 = .. ) blocks. 198 | func (d *Docs) Vars() []doc.Value { 199 | return d.indirectValues(d.Package.tc.docPkg.Vars) 200 | } 201 | 202 | // Funcs returns a slice of doc.Func representing exported functions. 203 | func (d *Docs) Funcs() []doc.Func { 204 | s := d.Package.tc.docPkg.Funcs 205 | out := make([]doc.Func, len(s)) 206 | for i := range s { 207 | out[i] = *s[i] 208 | } 209 | return out 210 | } 211 | 212 | // Var represents a packages top level named variable. 213 | type Var struct { 214 | *types.Var 215 | Named *types.Named 216 | } 217 | 218 | // NewVar returns a Var, typeVar must not be nil. 219 | func NewVar(typeVar *types.Var, typeNamed *types.Named) Var { 220 | return Var{Var: typeVar, Named: typeNamed} 221 | } 222 | 223 | // Vars returns all the packages named variables from the package scope. 224 | func (p *Package) Vars() []Var { 225 | p.init() 226 | var vars []Var 227 | scope := p.tc.typesPkg.Scope() 228 | for _, name := range scope.Names() { 229 | obj := scope.Lookup(name) 230 | if !obj.Exported() || isTest(name, "Test") || isTest(name, "Example") { 231 | continue 232 | } 233 | asVar, ok := obj.(*types.Var) 234 | if !ok { 235 | continue 236 | } 237 | var indir types.Type 238 | indir, ok = obj.Type().(*types.Pointer) 239 | if ok { 240 | indir = indir.(*types.Pointer).Elem() 241 | } 242 | asNamed, ok := indir.(*types.Named) 243 | if !ok { 244 | asNamed, _ = asVar.Type().(*types.Named) 245 | } 246 | vars = append(vars, NewVar(asVar, asNamed)) 247 | } 248 | return vars 249 | } 250 | 251 | // Struct represents a named struct. 252 | type Struct struct { 253 | *types.Struct 254 | Named *types.Named 255 | } 256 | 257 | // NewStruct returns a Struct, typeStruct must not be nil. 258 | func NewStruct(typeStruct *types.Struct, typeNamed *types.Named) Struct { 259 | return Struct{Struct: typeStruct, Named: typeNamed} 260 | } 261 | 262 | // Structs returns all the packages named structs from the package scope. 263 | func (p *Package) Structs() []Struct { 264 | p.init() 265 | var structs []Struct 266 | scope := p.tc.typesPkg.Scope() 267 | for _, name := range scope.Names() { 268 | obj := scope.Lookup(name) 269 | if !obj.Exported() || isTest(name, "Test") || isTest(name, "Example") { 270 | continue 271 | } 272 | asTypeName, ok := obj.(*types.TypeName) 273 | if !ok { 274 | continue 275 | } 276 | asNamed, ok := asTypeName.Type().(*types.Named) 277 | if !ok { 278 | continue 279 | } 280 | asStruct, ok := asNamed.Underlying().(*types.Struct) 281 | if !ok { 282 | continue 283 | } 284 | structs = append(structs, NewStruct(asStruct, asNamed)) 285 | } 286 | return structs 287 | } 288 | 289 | // Func groups a types.Func and types.Signature, it will never be part of a 290 | // method so Recv() will always be nul. 291 | type Func struct { 292 | *types.Func 293 | *types.Signature 294 | } 295 | 296 | // String implements fmt.Stringer. 297 | func (f Func) String() string { 298 | return f.Func.String() 299 | } 300 | 301 | // NewFunc returns a Function, typeFunc must not be nil. 302 | func NewFunc(typeFunc *types.Func) Func { 303 | // funcs always have signatures 304 | return Func{typeFunc, typeFunc.Type().(*types.Signature)} 305 | } 306 | 307 | // Funcs returns all the packages named functions from the packages outer 308 | // most scope. 309 | func (p *Package) Funcs() []Func { 310 | p.init() 311 | var funcs []Func 312 | scope := p.tc.typesPkg.Scope() 313 | for _, name := range scope.Names() { 314 | obj := scope.Lookup(name) 315 | if !obj.Exported() || isTest(name, "Test") || isTest(name, "Example") { 316 | continue 317 | } 318 | asFunc, ok := obj.(*types.Func) 319 | if !ok { 320 | continue 321 | } 322 | funcs = append(funcs, NewFunc(asFunc)) 323 | } 324 | return funcs 325 | } 326 | 327 | // MethodSet represents a set of methods belonging to a named type. 328 | type MethodSet struct { 329 | Name string 330 | Obj types.Object 331 | Methods map[string]Func 332 | } 333 | 334 | // NewMethodSet returns a initialized MethodSet. 335 | func NewMethodSet(name string, obj types.Object) MethodSet { 336 | return MethodSet{ 337 | Name: name, 338 | Obj: obj, 339 | Methods: make(map[string]Func), 340 | } 341 | } 342 | 343 | // Names returns the names of the methods in this MethodSet. 344 | func (m MethodSet) Names() []string { 345 | var names []string 346 | for k := range m.Methods { 347 | names = append(names, k) 348 | } 349 | sort.Strings(names) 350 | return names 351 | } 352 | 353 | // Len returns the current number of Method's within this MethodSet. 354 | func (m MethodSet) Len() int { 355 | return len(m.Methods) 356 | } 357 | 358 | // Methods returns a map keyed off of the name type with a value of MethodSet. 359 | // Only types with at least one method are included. 360 | func (p *Package) Methods() map[string]MethodSet { 361 | p.init() 362 | methods := make(map[string]MethodSet) 363 | scope := p.tc.typesPkg.Scope() 364 | for _, name := range scope.Names() { 365 | methodSet, err := p.MethodSet(name) 366 | if err != nil { 367 | continue 368 | } 369 | if methodSet.Len() > 0 { 370 | methods[name] = methodSet 371 | } 372 | } 373 | return methods 374 | } 375 | 376 | // MethodSet returns the set of methods for the given name. 377 | func (p *Package) MethodSet(name string) (MethodSet, error) { 378 | p.init() 379 | obj := p.tc.typesPkg.Scope().Lookup(name) 380 | if obj == nil { 381 | return MethodSet{}, fmt.Errorf("named type was not found") 382 | } 383 | if !obj.Exported() || isTest(name, "Test") || isTest(name, "Example") { 384 | return MethodSet{}, fmt.Errorf("named type was not exported") 385 | } 386 | 387 | typ := obj.Type() 388 | ms := NewMethodSet(name, obj) 389 | for _, t := range []types.Type{typ, types.NewPointer(typ)} { 390 | mset := types.NewMethodSet(t) 391 | for i := 0; i < mset.Len(); i++ { 392 | z := mset.At(i) 393 | f, ok := z.Obj().(*types.Func) 394 | if !ok { 395 | continue // must be *Var field selection 396 | } 397 | ms.Methods[f.Name()] = NewFunc(f) 398 | } 399 | } 400 | return ms, nil 401 | } 402 | 403 | // init is called for you by all functions and methods that return a Package 404 | // type, init will be ran only once within a sync.Once, multiple calls are safe. 405 | func (p *Package) init() (err error) { 406 | p.once.Do(func() { 407 | p.tc, err = p.toToolchain(p.typesInfo()) 408 | }) 409 | return err 410 | } 411 | 412 | // toToolchain is used to initialize the package for usage. 413 | func (p *Package) toToolchain(typesInfo *types.Info) (*toolchain, error) { 414 | tc := &toolchain{} 415 | fileSet := token.NewFileSet() 416 | pkgs, err := parser.ParseDir(fileSet, p.Dir, nil, DefaultParseMode) 417 | if err != nil { 418 | return nil, err 419 | } 420 | 421 | // @TODO I'm not sure the best way to Copy() an ast. There may be a utility 422 | // func somewhere but I couldn't find it, this will have to do for now. 423 | // -> doc.New takes ownership of the AST pkg and may edit or overwrite it. 424 | docFileSet := token.NewFileSet() 425 | docPkgs, err := parser.ParseDir(docFileSet, p.Dir, nil, DefaultParseMode) 426 | if err != nil { 427 | return nil, err 428 | } 429 | 430 | astPkg, okAst := pkgs[p.Name] 431 | docAstPkg, okDoc := docPkgs[p.Name] 432 | if !okAst || !okDoc { 433 | return tc, fmt.Errorf( 434 | `unable to find pkg "%s" in the "%s" directory`, p.Name, p.Dir) 435 | } 436 | docPkg := doc.New(docAstPkg, p.Dir, doc.Mode(0)) 437 | astFiles := p.astFiles(astPkg) 438 | conf := types.Config{Importer: importer.Default()} 439 | typesPkg, err := conf.Check(p.Name, fileSet, astFiles, typesInfo) 440 | if err != nil { 441 | return nil, err 442 | } 443 | 444 | tc.fileSet, tc.astPkg, tc.docPkg, tc.typesPkg, tc.typesInfo = 445 | fileSet, astPkg, docPkg, typesPkg, typesInfo 446 | return tc, nil 447 | } 448 | 449 | func (p *Package) astFiles(astPkg *ast.Package) (out []*ast.File) { 450 | for key := range astPkg.Files { 451 | out = append(out, astPkg.Files[key]) 452 | } 453 | return 454 | } 455 | 456 | // Exact check for a test func string from: 457 | // https://golang.org/src/cmd/go/test.go 458 | // 459 | // isTest tells whether name looks like a test (or benchmark, according to prefix). 460 | // It is a Test (say) if there is a character after Test that is not a lower-case letter. 461 | // We don't want TesticularCancer. 462 | // ^ 463 | // lol / 464 | // / 465 | func isTest(name, prefix string) bool { 466 | if !strings.HasPrefix(name, prefix) { 467 | return false 468 | } 469 | if len(name) == len(prefix) { // "Test" is ok 470 | return true 471 | } 472 | rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) 473 | return !unicode.IsLower(rune) 474 | } 475 | 476 | // Files groups operations on a packages files. 477 | type Files struct { 478 | Package *Package 479 | } 480 | 481 | // Files returns a sorted slice of full file paths for this package. 482 | func (p *Package) Files() Files { 483 | return Files{p} 484 | } 485 | 486 | // Names returns a sorted slice of file names for this package. 487 | func (pf *Files) Names() []string { 488 | pf.Package.init() 489 | s := pf.Package.tc.docPkg.Filenames 490 | out := make([]string, len(s)) 491 | for i := range s { 492 | out[i] = filepath.Base(s[i]) 493 | } 494 | return out 495 | } 496 | 497 | // Paths returns a sorted slice of full file paths for this package. 498 | func (pf *Files) Paths() []string { 499 | pf.Package.init() 500 | s := pf.Package.tc.docPkg.Filenames 501 | out := make([]string, len(s)) 502 | copy(out, s) 503 | return out 504 | } 505 | 506 | // SourcePaths is like FilePaths but will include all files found by the build 507 | // importer, .cc, .m, .s, etc while excluding test files. 508 | func (pf *Files) SourcePaths() []string { 509 | var names []string 510 | p := pf.Package 511 | names = append(names, append(p.GoFiles, p.CgoFiles...)...) 512 | names = append(names, append(p.CXXFiles, p.MFiles...)...) 513 | names = append(names, append(p.SFiles, p.SwigFiles...)...) 514 | paths := make([]string, len(names)) 515 | for i, name := range names { 516 | paths[i] = filepath.Join(p.Dir, name) 517 | } 518 | sort.Strings(paths) 519 | return paths 520 | } 521 | 522 | // TestPaths is like FilePaths but will include only test files. 523 | func (pf *Files) TestPaths() []string { 524 | var names []string 525 | p := pf.Package 526 | names = append(names, append(p.TestGoFiles, p.XTestGoFiles...)...) 527 | paths := make([]string, len(names)) 528 | for i, name := range names { 529 | paths[i] = filepath.Join(p.Dir, name) 530 | } 531 | sort.Strings(paths) 532 | return paths 533 | } 534 | --------------------------------------------------------------------------------