Skip to content

Commit

Permalink
Merge pull request #48 from Halleck45/feat_lint_mi
Browse files Browse the repository at this point in the history
Feat: maintainability linter
  • Loading branch information
Halleck45 committed Apr 19, 2024
2 parents a407015 + fd8c2c0 commit 67fb9e5
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 110 deletions.
5 changes: 4 additions & 1 deletion .ast-metrics.dist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ reports:
requirements:
rules:
cyclomatic_complexity:
max: 10
max: 100
excludes: []

lines_of_code:
max: 100
excludes: []

maintainability:
min: 65
5 changes: 5 additions & 0 deletions src/Analyzer/Component/MaintainabilityIndexVisitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func (v *MaintainabilityIndexVisitor) Calculate(stmts *pb.Stmts) {
stmts.Analyze.Maintainability = &pb.Maintainability{}
}

if loc == 0 {
// when class has no code
MI32 = float32(171)
}

stmts.Analyze.Maintainability.MaintainabilityIndex = &MI32
stmts.Analyze.Maintainability.MaintainabilityIndexWithoutComments = &MIwoC32
stmts.Analyze.Maintainability.CommentWeight = &commentWeight32
Expand Down
109 changes: 63 additions & 46 deletions src/Analyzer/RequirementsEvaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ func NewRequirementsEvaluator(requirements Configuration.ConfigurationRequiremen
return &RequirementsEvaluator{Requirements: requirements}
}

// Rules with boundaries, to evaluate
type BoundariesRule struct {
Name string
Rule *Configuration.ConfigurationDefaultRule
Label string
}

func (r *RequirementsEvaluator) Evaluate(files []*pb.File, projectAggregated ProjectAggregated) EvaluationResult {
evaluation := EvaluationResult{
Files: files,
Expand All @@ -37,70 +44,62 @@ func (r *RequirementsEvaluator) Evaluate(files []*pb.File, projectAggregated Pro
return evaluation
}

// Cyclomatic
if r.Requirements.Rules.CyclomaticComplexity != nil {
cyclomatic := r.Requirements.Rules.CyclomaticComplexity
excludedFiles := cyclomatic.ExcludePatterns
for _, file := range files {

// if the file is excluded, we skip it (use regular expression)
excluded := false
if excludedFiles != nil {
for _, pattern := range excludedFiles {
if regexp.MustCompile(pattern).MatchString(file.Path) {
excluded = true
break
}
}
}
rulesToCheck := []BoundariesRule{
{Name: "cyclomatic", Rule: r.Requirements.Rules.CyclomaticComplexity, Label: "Cyclomatic complexity"},
{Name: "loc", Rule: r.Requirements.Rules.Loc, Label: "Lines of code"},
{Name: "maintainability", Rule: r.Requirements.Rules.Maintainability, Label: "Maintainability"},
}

if excluded {
continue
}
for _, rule := range rulesToCheck {

if file.Stmts.Analyze.Complexity == nil {
continue
}

if int(*file.Stmts.Analyze.Complexity.Cyclomatic) > cyclomatic.Max {
evaluation.Errors = append(evaluation.Errors, fmt.Sprintf("Cyclomatic complexity too high in file %s: got %d (max: %d)", file.Path, *file.Stmts.Analyze.Complexity.Cyclomatic, cyclomatic.Max))
} else {
evaluation.Successes = append(evaluation.Successes, "Cyclomatic complexity OK in file "+file.Path)
}
if rule.Rule == nil {
continue
}
}

// Lines of code
if r.Requirements.Rules.Loc != nil {
loc := r.Requirements.Rules.Loc
excludedFiles := loc.ExcludePatterns
excludedFiles := []string{}
if rule.Rule.ExcludePatterns != nil {
excludedFiles = rule.Rule.ExcludePatterns
}

for _, file := range files {

// if the file is excluded, we skip it (use regular expression)
excluded := false
if excludedFiles != nil {
for _, pattern := range excludedFiles {
if regexp.MustCompile(pattern).MatchString(file.Path) {
excluded = true
break
}
for _, pattern := range excludedFiles {
if regexp.MustCompile(pattern).MatchString(file.Path) {
excluded = true
break
}
}

if excluded {
continue
}

if file.Stmts.Analyze == nil || file.Stmts.Analyze.Volume.Loc == nil {
continue
valueOfMetric := 0
switch rule.Name {
case "cyclomatic":
if file.Stmts.Analyze.Complexity == nil || file.Stmts.Analyze.Complexity.Cyclomatic == nil {
continue
}
valueOfMetric = int(*file.Stmts.Analyze.Complexity.Cyclomatic)
r.EvaluateRule(rule, valueOfMetric, file, &evaluation)
case "loc":
if file.Stmts.Analyze.Volume == nil || file.Stmts.Analyze.Volume.Loc == nil {
continue
}
valueOfMetric = int(*file.Stmts.Analyze.Volume.Loc)
r.EvaluateRule(rule, valueOfMetric, file, &evaluation)
case "maintainability":
for _, class := range file.Stmts.StmtClass {
if class.Stmts.Analyze.Maintainability == nil || class.Stmts.Analyze.Maintainability.MaintainabilityIndex == nil {
continue
}
valueOfMetric = int(*class.Stmts.Analyze.Maintainability.MaintainabilityIndex)
r.EvaluateRule(rule, valueOfMetric, file, &evaluation)
}
}

if int(*file.Stmts.Analyze.Volume.Loc) > loc.Max {
evaluation.Errors = append(evaluation.Errors, fmt.Sprintf("Lines of code too high in file %s: got %d (max: %d)", file.Path, *file.Stmts.Analyze.Volume.Loc, loc.Max))
} else {
evaluation.Successes = append(evaluation.Successes, "Lines of code OK in file "+file.Path)
}
}
}

Expand Down Expand Up @@ -146,3 +145,21 @@ func (r *RequirementsEvaluator) Evaluate(files []*pb.File, projectAggregated Pro

return evaluation
}

func (r *RequirementsEvaluator) EvaluateRule(rule BoundariesRule, valueOfMetric int, file *pb.File, evaluation *EvaluationResult) {

maxExpected := rule.Rule.Max
minExpected := rule.Rule.Min

if maxExpected > 0 && valueOfMetric > maxExpected {
evaluation.Errors = append(evaluation.Errors, fmt.Sprintf("%s too high in file %s: got %d (max: %d)", rule.Label, file.Path, valueOfMetric, maxExpected))
return
}

if minExpected > 0 && valueOfMetric < minExpected {
evaluation.Errors = append(evaluation.Errors, fmt.Sprintf("%s too low in file %s: got %d (min: %d)", rule.Label, file.Path, valueOfMetric, minExpected))
return
}

evaluation.Successes = append(evaluation.Successes, fmt.Sprintf("%s OK in file %s", rule.Label, file.Path))
}
104 changes: 104 additions & 0 deletions src/Analyzer/RequirementsEvaluator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package Analyzer

import (
"testing"

"github.com/halleck45/ast-metrics/src/Configuration"
pb "github.com/halleck45/ast-metrics/src/NodeType"
"github.com/stretchr/testify/assert"
)

func TestEvaluationResult(t *testing.T) {

ccn5 := int32(5)
ccn10 := int32(10)
files := []*pb.File{
{
Path: "test1.go",
Stmts: &pb.Stmts{
Analyze: &pb.Analyze{
Complexity: &pb.Complexity{
Cyclomatic: &ccn10,
},
},
},
},
{
Path: "test2.go",
Stmts: &pb.Stmts{
Analyze: &pb.Analyze{
Complexity: &pb.Complexity{
Cyclomatic: &ccn5,
},
},
},
},
}

configInYaml := `
requirements:
rules:
cyclomatic_complexity:
max: 5
`

loader := Configuration.NewConfigurationLoader()
configuration, err := loader.Import(configInYaml)
assert.Nil(t, err)

evaluator := NewRequirementsEvaluator(*configuration.Requirements)
evaluation := evaluator.Evaluate(files, ProjectAggregated{})

assert.Equal(t, files, evaluation.Files)
assert.Equal(t, false, evaluation.Succeeded)

assert.Equal(t, 1, len(evaluation.Errors))
assert.Equal(t, "Cyclomatic complexity too high in file test1.go: got 10 (max: 5)", evaluation.Errors[0])
}

func TestEvaluationResultSuccess(t *testing.T) {

ccn5 := int32(5)
ccn10 := int32(10)
files := []*pb.File{
{
Path: "test1.go",
Stmts: &pb.Stmts{
Analyze: &pb.Analyze{
Complexity: &pb.Complexity{
Cyclomatic: &ccn10,
},
},
},
},
{
Path: "test2.go",
Stmts: &pb.Stmts{
Analyze: &pb.Analyze{
Complexity: &pb.Complexity{
Cyclomatic: &ccn5,
},
},
},
},
}

configInYaml := `
requirements:
rules:
cyclomatic_complexity:
max: 15
`

loader := Configuration.NewConfigurationLoader()
configuration, err := loader.Import(configInYaml)
assert.Nil(t, err)

evaluator := NewRequirementsEvaluator(*configuration.Requirements)
evaluation := evaluator.Evaluate(files, ProjectAggregated{})

assert.Equal(t, files, evaluation.Files)
assert.Equal(t, true, evaluation.Succeeded)

assert.Equal(t, 0, len(evaluation.Errors))
}
2 changes: 2 additions & 0 deletions src/Configuration/Configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type ConfigurationRequirements struct {
Rules *struct {
CyclomaticComplexity *ConfigurationDefaultRule `yaml:"cyclomatic_complexity"`
Loc *ConfigurationDefaultRule `yaml:"loc"`
Maintainability *ConfigurationDefaultRule `yaml:"maintainability"`
Coupling *struct {
Forbidden []struct {
From string `yaml:"from"`
Expand All @@ -43,6 +44,7 @@ type ConfigurationRequirements struct {

type ConfigurationDefaultRule struct {
Max int `yaml:"max"`
Min int `yaml:"min"`
ExcludePatterns []string `yaml:"exclude"`
}

Expand Down

0 comments on commit 67fb9e5

Please sign in to comment.