Skip to content

Commit 2d01cdd

Browse files
committed
Initial commit
0 parents  commit 2d01cdd

File tree

7 files changed

+567
-0
lines changed

7 files changed

+567
-0
lines changed

LICENSE

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2016, Zac Bergquist
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
* Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
* Neither the name of gogetdoc nor the names of its
15+
contributors may be used to endorse or promote products derived from
16+
this software without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
gogetdoc
2+
========
3+
4+
Retrieve documentation for items in Go source code.
5+
6+
Go has a variety of tools that make it easy to lookup documentation.
7+
There's the `godoc` HTTP server, the `go doc` command line tool, and https://godoc.org.
8+
9+
These tools are great, but in many cases one may find it valuable to lookup
10+
documentation right from their editor. The problem with all of these tools
11+
is that they are all meant to be used by a person who knows what they are
12+
looking for. This makes editor integration difficult, as there isn't an easy way
13+
to say "get me the documentation for this item here."
14+
15+
The `gogetdoc` tool aims to make it easier for editors to provide access to
16+
Go documentation. Simply give it a filename and offset within the file and
17+
it will figure out what what you're referring to and find the documentation
18+
for it.
19+
20+
## Prerequisites
21+
22+
This tool **requires Go 1.6**, which is currently available by building from tip
23+
or by installing the Go 1.6 release candidate from https://golang.org/dl/.
24+
25+
## Contributions
26+
27+
Are more than welcome! For small changes feel free to open a pull request.
28+
For larger changes or major features please open an issue to discuss.
29+
30+
## Credits
31+
32+
The following resources served as both inspiration for starting this tool
33+
and help coming up with the implementation.
34+
35+
- Alan Donovan's GothamGo talk "Using `go/types` for Code Comprehension
36+
and Refactoring Tools" https://youtu.be/p_cz7AxVdfg
37+
- Fatih Arslan's talk at dotGo 2015 "Tools for working with Go Code"
38+
- The `go/types` example repository: https://github.com/golang/example/tree/master/gotypes
39+
40+
## License
41+
42+
3-Clause BSD license - see the LICENSE file for details.

ident.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"go/token"
7+
"go/types"
8+
9+
"golang.org/x/tools/go/loader"
10+
)
11+
12+
// IdentDoc attempts to get the documentation for a *ast.Ident.
13+
func IdentDoc(id *ast.Ident, info *loader.PackageInfo, prog *loader.Program) (*Doc, error) {
14+
// get definition of identifier
15+
obj := info.ObjectOf(id)
16+
17+
// handle packages imported under a different name
18+
if p, ok := obj.(*types.PkgName); ok {
19+
return PackageDoc(prog.Fset, p.Imported().Path())
20+
}
21+
22+
_, nodes, _ := prog.PathEnclosingInterval(obj.Pos(), obj.Pos())
23+
for _, node := range nodes {
24+
//fmt.Printf("for %s: found %T\n%#v\n", id.Name, node, node)
25+
switch n := node.(type) {
26+
case *ast.FuncDecl:
27+
return &Doc{
28+
Name: obj.Name(),
29+
Title: obj.String(), // TODO "relative-to" output format...
30+
Doc: n.Doc.Text(),
31+
}, nil
32+
case *ast.GenDecl:
33+
var constValue string
34+
if n.Tok == token.CONST {
35+
SpecLoop:
36+
for _, s := range n.Specs {
37+
vs := s.(*ast.ValueSpec)
38+
for _, val := range vs.Values {
39+
if bl, ok := val.(*ast.BasicLit); ok {
40+
if bl.Value != "" {
41+
constValue = bl.Value
42+
break SpecLoop
43+
}
44+
}
45+
}
46+
}
47+
}
48+
if n.Doc != nil {
49+
d := &Doc{
50+
Name: obj.Name(),
51+
Title: obj.String(),
52+
Doc: n.Doc.Text(),
53+
}
54+
if constValue != "" {
55+
d.Doc += fmt.Sprintf("\nConstant Value: %s", constValue)
56+
}
57+
return d, nil
58+
}
59+
case *ast.Field:
60+
// check the doc first, if not present, then look for a comment
61+
if n.Doc != nil {
62+
return &Doc{
63+
Name: obj.Name(),
64+
Title: obj.String(),
65+
Doc: n.Doc.Text(),
66+
}, nil
67+
} else if n.Comment != nil {
68+
return &Doc{
69+
Name: obj.Name(),
70+
Title: obj.String(),
71+
Doc: n.Comment.Text(),
72+
}, nil
73+
}
74+
}
75+
}
76+
return nil, fmt.Errorf("No documentation found for %s", obj.Name())
77+
}

ident_test.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"go/ast"
5+
"go/parser"
6+
"go/token"
7+
"strings"
8+
"testing"
9+
10+
"golang.org/x/tools/go/loader"
11+
)
12+
13+
const funcDecl = `package main
14+
15+
import (
16+
"fmt"
17+
mth "math"
18+
)
19+
20+
type X struct{}
21+
22+
// SayHello says hello.
23+
func (X) SayHello() {
24+
fmt.Println("Hello, World", mth.IsNaN(1.23))
25+
}
26+
27+
func main() {
28+
var x X
29+
x.SayHello()
30+
SayGoodbye()
31+
}
32+
33+
// SayGoodbye says goodbye.
34+
func SayGoodbye() {
35+
fmt.Println("Goodbye")
36+
fmt.Println(Message, fmt.Sprintf("The answer is %d", Answer))
37+
}
38+
39+
// Message is a message.
40+
var Message = "This is a test."
41+
42+
// Answer is the answer to life the universe and everything.
43+
const Answer = 42
44+
45+
type Foo struct {
46+
// FieldA has doc
47+
FieldA string
48+
FieldB string // FieldB has a comment
49+
}
50+
51+
func (f Foo) Print() {
52+
fmt.Println(f.FieldA, f.FieldB)
53+
}
54+
`
55+
56+
func TestIdent(t *testing.T) {
57+
t.Parallel()
58+
conf := &loader.Config{
59+
ParserMode: parser.ParseComments,
60+
}
61+
astFile, err := conf.ParseFile("test.go", funcDecl)
62+
if err != nil {
63+
t.Error(err)
64+
}
65+
66+
conf.CreateFromFiles("main", astFile)
67+
prog, err := conf.Load()
68+
if err != nil {
69+
t.Error(err)
70+
}
71+
72+
tokFile := FileFromProgram(prog, "test.go")
73+
if tokFile == nil {
74+
t.Error("Couldn't get token.File from program")
75+
}
76+
77+
tests := []struct {
78+
Pos token.Pos
79+
Doc string
80+
}{
81+
{tokFile.Pos(191), "SayHello says hello.\n"}, // method call
82+
{tokFile.Pos(205), "SayGoodbye says goodbye.\n"}, // function call
83+
{tokFile.Pos(305), "Message is a message.\n"}, // var (use)
84+
{tokFile.Pos(388), "Message is a message.\n"}, // var (definition)
85+
{tokFile.Pos(318), "Sprintf formats according to a format specifier and returns the resulting string.\n"}, // std func
86+
{tokFile.Pos(346), "Answer is the answer to life the universe and everything.\n\nConstant Value: 42"}, // const (use)
87+
{tokFile.Pos(484), "Answer is the answer to life the universe and everything.\n\nConstant Value: 42"}, // const (definition)
88+
{tokFile.Pos(144), "IsNaN reports whether f is an IEEE 754 ``not-a-number'' value.\n"}, // std func call (alias import)
89+
{tokFile.Pos(628), "FieldA has doc\n"},
90+
{tokFile.Pos(637), "FieldB has a comment\n"},
91+
}
92+
TestLoop:
93+
for _, test := range tests {
94+
info, nodes, _ := prog.PathEnclosingInterval(test.Pos, test.Pos)
95+
for i := range nodes {
96+
if ident, ok := nodes[i].(*ast.Ident); ok {
97+
doc, err := IdentDoc(ident, info, prog)
98+
if err != nil {
99+
t.Fatal(err)
100+
}
101+
if !strings.EqualFold(test.Doc, doc.Doc) {
102+
t.Errorf("Want '%s', got '%s'\n", test.Doc, doc.Doc)
103+
}
104+
continue TestLoop
105+
}
106+
}
107+
t.Errorf("Coudln't find *ast.Ident at %s\n", prog.Fset.Position(test.Pos))
108+
}
109+
}

0 commit comments

Comments
 (0)