mirror of https://github.com/ceph/go-ceph
implements: add initial code for process Go sources
This code makes use of Go's native AST processing packages and allows us to analyze our source code using the go-ceph conventions. Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
parent
ce7911ea35
commit
18d8083de3
|
@ -0,0 +1,124 @@
|
|||
package implements
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type visitor struct {
|
||||
inFunction *ast.FuncDecl
|
||||
|
||||
callMap map[string]string
|
||||
docMap map[string]string
|
||||
}
|
||||
|
||||
func newVisitor() *visitor {
|
||||
return &visitor{
|
||||
callMap: map[string]string{},
|
||||
docMap: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *visitor) checkDocImplements(fdec *ast.FuncDecl) {
|
||||
dtext := fdec.Doc.Text()
|
||||
lines := strings.Split(dtext, "\n")
|
||||
for i := range lines {
|
||||
if lines[i] == "Implements:" {
|
||||
cfunc := cfuncFromComment(lines[i+1])
|
||||
if cfunc == "" {
|
||||
return
|
||||
}
|
||||
v.docMap[cfunc] = fdec.Name.Name
|
||||
logger.Printf("updated %s in doc map\n", cfunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *visitor) checkCalled(s *ast.SelectorExpr) {
|
||||
ident, ok := s.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if "C" == ident.String() {
|
||||
v.callMap[s.Sel.String()] = v.inFunction.Name.Name
|
||||
logger.Printf("updated %s in call map\n", s.Sel.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
||||
switch {
|
||||
case node == nil:
|
||||
return nil
|
||||
case v.inFunction == nil:
|
||||
case node.Pos() > v.inFunction.End():
|
||||
logger.Printf("left function %v\n", v.inFunction.Name.Name)
|
||||
v.inFunction = nil
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case *ast.File:
|
||||
v.inFunction = nil
|
||||
return v
|
||||
case *ast.FuncDecl:
|
||||
logger.Printf("checking function: %v\n", n.Name.Name)
|
||||
v.checkDocImplements(n)
|
||||
v.inFunction = n
|
||||
return v
|
||||
case *ast.CallExpr:
|
||||
if v.inFunction == nil {
|
||||
return nil
|
||||
}
|
||||
if s, ok := n.Fun.(*ast.SelectorExpr); ok {
|
||||
v.checkCalled(s)
|
||||
}
|
||||
}
|
||||
if v.inFunction != nil {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cfuncFromComment(ctext string) string {
|
||||
m := regexp.MustCompile(` ([a-zA-Z0-9_]+)\(`).FindAllSubmatch([]byte(ctext), 1)
|
||||
if len(m) < 1 {
|
||||
return ""
|
||||
}
|
||||
return string(m[0][1])
|
||||
}
|
||||
|
||||
// CephGoFunctions will look for C functions called by the code code and
|
||||
// update the found functions for the package within the inspector.
|
||||
func CephGoFunctions(source, packageName string, ii *Inspector) error {
|
||||
p, err := build.Import("./"+packageName, source, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toCheck := []string{}
|
||||
toCheck = append(toCheck, p.GoFiles...)
|
||||
toCheck = append(toCheck, p.CgoFiles...)
|
||||
for _, fname := range toCheck {
|
||||
logger.Printf("Reading go file: %v\n", fname)
|
||||
src, err := ioutil.ReadFile(path.Join(p.Dir, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(
|
||||
fset,
|
||||
fname,
|
||||
src,
|
||||
parser.ParseComments|parser.AllErrors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ast.Walk(ii.visitor, f)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package implements
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type foundFlags int
|
||||
|
||||
const (
|
||||
isCalled = foundFlags(1)
|
||||
isDocumented = foundFlags(2)
|
||||
isDeprecated = foundFlags(4)
|
||||
)
|
||||
|
||||
// Inspector types collect the high-level results from C and Go
|
||||
// code scans.
|
||||
type Inspector struct {
|
||||
visitor *visitor
|
||||
|
||||
expected CFunctions
|
||||
found map[string]foundFlags
|
||||
deprecatedMissing int
|
||||
}
|
||||
|
||||
// SetExpected sets the expected C functions, asuming the supplied prefix.
|
||||
func (ii *Inspector) SetExpected(prefix string, expected CFunctions) error {
|
||||
ii.expected = make([]CFunction, 0, len(expected))
|
||||
for _, cfunc := range expected {
|
||||
if strings.HasPrefix(cfunc.Name, prefix) {
|
||||
logger.Printf("C function \"%s\" has matching prefix", cfunc.Name)
|
||||
ii.expected = append(ii.expected, cfunc)
|
||||
}
|
||||
}
|
||||
_, err := ii.expected.ensure()
|
||||
return err
|
||||
}
|
||||
|
||||
func (ii *Inspector) update() {
|
||||
ii.found = map[string]foundFlags{}
|
||||
ii.deprecatedMissing = 0
|
||||
for i := range ii.expected {
|
||||
n := ii.expected[i].Name
|
||||
if _, found := ii.visitor.callMap[n]; found {
|
||||
ii.found[n] |= isCalled
|
||||
}
|
||||
if _, found := ii.visitor.docMap[n]; found {
|
||||
ii.found[n] |= isDocumented
|
||||
}
|
||||
if ii.expected[i].isDeprecated() {
|
||||
if _, found := ii.found[n]; found {
|
||||
ii.found[n] |= isDeprecated
|
||||
} else {
|
||||
ii.deprecatedMissing++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewInspector returns a newly created code inspector object.
|
||||
func NewInspector() *Inspector {
|
||||
return &Inspector{
|
||||
visitor: newVisitor(),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue