metrieke/java.go

203 lines
4.4 KiB
Go

package metrieke
import (
"fmt"
"io/ioutil"
"strings"
sitter "github.com/smacker/go-tree-sitter"
"github.com/smacker/go-tree-sitter/java"
)
type JavaFile struct {
Source []byte
Path string
Root *sitter.Node
Metrics *JavaFileMetrics
}
type JavaFileMetrics struct {
Loc int
Lloc int
Dloc int
Cloc int
DocumentationLines int
AvgComplexity float64
SumComplexity int
AvgNesting float64
SumNesting int
}
func NewJavaFileFromContents(path string, contents []byte) *JavaFile {
parser := sitter.NewParser()
parser.SetLanguage(java.GetLanguage())
tree := parser.Parse(nil, contents)
node := tree.RootNode()
if tree == nil {
fmt.Printf("could not parse content")
}
if node == nil {
fmt.Printf("could not detect root node")
}
return &JavaFile{
Path: path,
Source: contents,
Root: node,
Metrics: &JavaFileMetrics{},
}
}
func NewJavaFile(path string) *JavaFile {
contents, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println("got error")
}
return NewJavaFileFromContents(path, contents)
}
func (jf *JavaFile) Parse() {
jf.javaLines()
//fmt.Printf(string(pf.Source))
numMethods := 0
sumComplexity := 0
var sumNesting int
var avgNesting float64
mccc := make([]int, 0)
pattern := []byte(`(method_declaration name: (identifier) @function-name)`)
queryChildNodes(pattern, java.GetLanguage(), jf.Root, func(node *sitter.Node) {
//fmt.Printf("node: %v\n", node.Parent())
complexity := javaMcCabeComplexity(node.Parent())
sumNesting += javaMaxNesting(node.Parent())
numMethods += 1
sumComplexity += complexity
mccc = append(mccc, complexity)
})
avgComplexity := float64(0)
if numMethods > 0 {
avgComplexity = float64(sumComplexity) / float64(numMethods)
avgNesting = float64(sumNesting) / float64(numMethods)
}
jf.Metrics.AvgComplexity = avgComplexity
jf.Metrics.SumComplexity = sumComplexity
jf.Metrics.SumNesting = sumNesting
jf.Metrics.AvgNesting = avgNesting
}
// handle the simple line stuff parsing
func (jf *JavaFile) javaLines() {
normalized := strings.ReplaceAll(string(jf.Source), "\r\n", "\n")
lines := strings.Split(normalized, "\n")
loc := len(lines)
lloc := 0
dloc := 0
cloc := 0
inDocumentation := false
inComment := false
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
if inDocumentation == false && strings.HasPrefix(trimmedLine, `/**`) {
inDocumentation = true
trimmedLine = strings.Replace(trimmedLine, `/**`, "", 1)
}
if inComment == false && strings.HasPrefix(trimmedLine, `/*`) {
inComment = true
trimmedLine = strings.Replace(trimmedLine, `/*`, "", 1)
}
if inDocumentation {
dloc += 1
}
if inComment {
cloc += 1
}
if inDocumentation && strings.HasSuffix(trimmedLine, `**/`) {
inDocumentation = false
continue
}
if inComment && strings.HasSuffix(trimmedLine, `*/`) {
inComment = false
continue
}
if strings.HasPrefix(trimmedLine, "//") {
cloc += 1
continue
}
if !inComment && !inDocumentation && trimmedLine != "" {
lloc += 1
}
}
jf.Metrics.Loc = loc
jf.Metrics.Lloc = lloc
jf.Metrics.Dloc = dloc
jf.Metrics.Cloc = cloc
}
func javaNumMethods(node *sitter.Node) int {
numMethods := 0
pattern := []byte(`(method_definition name: (identifier) @function-name)`)
queryChildNodes(pattern, java.GetLanguage(), node, func(node *sitter.Node) {
numMethods += 1
})
return numMethods
}
func javaMaxNesting(node *sitter.Node) int {
maxChild := -1
for i := 0; i < int(node.NamedChildCount()); i++ {
childDepth := javaMaxNesting(node.NamedChild(i))
if childDepth >= maxChild {
maxChild = childDepth
}
}
if node.Type() == "block" {
return maxChild + 1
} else {
return maxChild
}
}
// traverse down the method node to get the cyclomatic complexity
func javaMcCabeComplexity(node *sitter.Node) int {
iter := sitter.NewNamedIterator(node, sitter.BFSMode)
complexity := 1 // 1 per method
for {
n, err := iter.Next()
if err != nil {
return complexity
}
// +1 for every branch statement
if n.Type() == "if_statement" {
complexity += 1
}
if n.Type() == "for_statement" {
complexity += 1
}
if n.Type() == "while_statement" {
complexity += 1
}
if n.Type() == "switch_statement" { // could also go for switch_label
complexity += 1
}
if n.Type() == "catch_clause" {
complexity += 1
}
if n.Type() == "do_statement" {
complexity += 1
}
}
}