Skip to content

Commit

Permalink
Merge pull request #34 from Halleck45/feat_risk_for_procedural_languages
Browse files Browse the repository at this point in the history
feat: calculate risk for procedural languages
  • Loading branch information
Halleck45 committed Mar 26, 2024
2 parents 7f00d8b + 31c4227 commit 22f6035
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 20 deletions.
17 changes: 17 additions & 0 deletions src/Analyzer/Aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,23 @@ func (r *Aggregator) consolidate(aggregated *Aggregated) {
averageForFile = averageForFile / float32(len(methods))
file.Stmts.Analyze.Maintainability.MaintainabilityIndex = &averageForFile
}

// File analysis should be the sum of all methods and classes in the file
// That's useful when we navigate over the files instead of the classes
functions := Engine.GetFunctionsInFile(file)
zero := int32(0)
if file.Stmts.Analyze.Complexity.Cyclomatic == nil {
file.Stmts.Analyze.Complexity.Cyclomatic = &zero
}
for _, function := range functions {
if function.Stmts.Analyze == nil || function.Stmts.Analyze.Complexity == nil {
continue
}
if function.Stmts.Analyze.Complexity != nil {

*file.Stmts.Analyze.Complexity.Cyclomatic += *function.Stmts.Analyze.Complexity.Cyclomatic
}
}
}

// Count commits for the period based on `ResultOfGitAnalysis` data
Expand Down
42 changes: 42 additions & 0 deletions src/Analyzer/Complexity/CyclomaticVisitor_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package Analyzer

import (
"os"
"testing"

"github.com/halleck45/ast-metrics/src/Engine/Golang"
pb "github.com/halleck45/ast-metrics/src/NodeType"
"google.golang.org/protobuf/encoding/protojson"
)
Expand Down Expand Up @@ -258,3 +260,43 @@ func TestItCalculateCyclomaticComplexity(t *testing.T) {
t.Error("Expected 11, got ", ccn)
}
}

func TestItCalculateCyclomaticComplexityForNotObjectOrientedLanguages(t *testing.T) {

visitor := CyclomaticComplexityVisitor{}

fileContent := `
package main
import "fmt"
func example() {
if true {
if true {
fmt.Println("Hello")
}
} else if true {
fmt.Println("Hello")
} else {
fmt.Println("Hello")
}
}
`

// Create a temporary file
tmpFile := t.TempDir() + "/test.php"
if _, err := os.Create(tmpFile); err != nil {
t.Error(err)
}
if err := os.WriteFile(tmpFile, []byte(fileContent), 0644); err != nil {
t.Error(err)
}

pbFile := Golang.ParseGoFile(tmpFile)

ccn := visitor.Calculate(pbFile.Stmts)

if ccn != 3 {
t.Error("Expected 3, got ", ccn)
}
}
51 changes: 36 additions & 15 deletions src/Analyzer/RiskAnalyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func NewRiskAnalyzer() *RiskAnalyzer {
func (v *RiskAnalyzer) Analyze(project ProjectAggregated) {

maxComplexity := 0.0
maxCyclomatic := 0.0
maxCommits := 0.0

// get bounds
Expand All @@ -37,6 +38,12 @@ func (v *RiskAnalyzer) Analyze(project ProjectAggregated) {
}
}

// all files (procedural and OOP)
cyclomatic := float64(*file.Stmts.Analyze.Complexity.Cyclomatic)
if cyclomatic > maxCyclomatic {
maxCyclomatic = cyclomatic
}

if float64(len(commits)) > maxCommits {
maxCommits = float64(len(commits))
}
Expand All @@ -54,29 +61,43 @@ func (v *RiskAnalyzer) Analyze(project ProjectAggregated) {
nbCommits = len(file.Commits.Commits)
}

// OOP file
// OOP objects. We put risk on classes, according to the maintainability index.
for _, class := range Engine.GetClassesInFile(file) {

if class.Stmts == nil || class.Stmts.Analyze == nil || class.Stmts.Analyze.Maintainability == nil {
continue
}

// Calculate the horizontal and vertical distance from the "top right" corner.
horizontalDistance := maxCommits - float64(nbCommits)
verticalDistance := maxComplexity - float64(128-*class.Stmts.Analyze.Maintainability.MaintainabilityIndex)
risk := v.GetRisk(maxCommits, maxComplexity, nbCommits, int(128-*class.Stmts.Analyze.Maintainability.MaintainabilityIndex))
file.Stmts.Analyze.Risk.Score += float32(risk)
}

// Procedural file. We put risk on the file itself, according to the cyclomatic complexity.
if file.Stmts == nil || file.Stmts.Analyze == nil || file.Stmts.Analyze.Complexity == nil {
continue
}

cyclo := float64(*file.Stmts.Analyze.Complexity.Cyclomatic)
risk := v.GetRisk(maxCommits, maxCyclomatic, nbCommits, int(cyclo))
file.Stmts.Analyze.Risk.Score += float32(risk)
}
}

// Normalize these values over time, we first divide by the maximum values, to always end up with distances between 0 and 1.
normalizedHorizontalDistance := horizontalDistance / maxCommits
normalizedVerticalDistance := verticalDistance / maxComplexity
func (v *RiskAnalyzer) GetRisk(maxCommits float64, maxComplexity float64, nbCommits int, complexity int) float32 {

// Calculate the distance of this class from the "top right" corner, using the simple formula A^2 + B^2 = C^2; or: C = sqrt(A^2 + B^2)).
distanceFromTopRightCorner := math.Sqrt(math.Pow(normalizedHorizontalDistance, 2) + math.Pow(normalizedVerticalDistance, 2))
// Calculate the horizontal and vertical distance from the "top right" corner.
horizontalDistance := maxCommits - float64(nbCommits)
verticalDistance := maxComplexity - float64(complexity)

// The resulting value will be between 0 and sqrt(2). A short distance is bad, so in order to end up with a high score, we invert the value by subtracting it from 1.
risk := 1 - distanceFromTopRightCorner
class.Stmts.Analyze.Risk = &pb.Risk{Score: float32(risk)}
// Normalize these values over time, we first divide by the maximum values, to always end up with distances between 0 and 1.
normalizedHorizontalDistance := horizontalDistance / maxCommits
normalizedVerticalDistance := verticalDistance / maxComplexity

file.Stmts.Analyze.Risk.Score += float32(risk)
}
}
// Calculate the distance of this class from the "top right" corner, using the simple formula A^2 + B^2 = C^2; or: C = sqrt(A^2 + B^2)).
distanceFromTopRightCorner := math.Sqrt(math.Pow(normalizedHorizontalDistance, 2) + math.Pow(normalizedVerticalDistance, 2))

// The resulting value will be between 0 and sqrt(2). A short distance is bad, so in order to end up with a high score, we invert the value by subtracting it from 1.
risk := 1 - distanceFromTopRightCorner

return float32(risk)
}
2 changes: 1 addition & 1 deletion src/Cli/ComponentFileTable.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (v *ComponentFileTable) Init() {
if len(filename) > 60 {
filename = "..." + filename[len(filename)-57:]
// remove the extension

}

rows = append(rows, table.Row{
Expand Down
4 changes: 2 additions & 2 deletions src/Engine/Golang/GolangRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (r GolangRunner) DumpAST() {
}

// Create protobuf object
protoFile := parseGoFile(filePath)
protoFile := ParseGoFile(filePath)

// Dump protobuf object to destination
Engine.DumpProtobuf(protoFile, binPath)
Expand All @@ -87,7 +87,7 @@ func (r GolangRunner) DumpAST() {

}

func parseGoFile(filePath string) *pb.File {
func ParseGoFile(filePath string) *pb.File {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions src/Engine/Golang/GolangRunner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestGolangRunner(t *testing.T) {
t.Error(err)
}

result := parseGoFile(tmpFile)
result := ParseGoFile(tmpFile)

// Ensure path
assert.Equal(t, tmpFile, result.Path, "Expected path to be %s, got %s", tmpFile, result.Path)
Expand Down Expand Up @@ -118,7 +118,7 @@ func TestGoLangStructureExtraction(t *testing.T) {
t.Error(err)
}

result := parseGoFile(tmpFile)
result := ParseGoFile(tmpFile)
testedFunction := result.Stmts.StmtFunction[0]

// Ifs
Expand Down

0 comments on commit 22f6035

Please sign in to comment.