195 lines
4.3 KiB
Go
195 lines
4.3 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()
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|