From aa0e4fbd3a703fc178fe20ee1ba30c67895e0004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20Bayram?= <8135027+ismailbayram@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:39:28 +0300 Subject: [PATCH] added size validator --- .bigpicture.json | 7 +++ README.md | 14 ++++++ internal/browser/go.go | 2 + internal/browser/go_test.go | 14 +++++- internal/browser/java.go | 2 + internal/browser/java_test.go | 1 + internal/browser/python.go | 2 + internal/browser/python_test.go | 1 + internal/validators/linecount.go | 2 +- internal/validators/linecount_test.go | 10 ++--- internal/validators/size.go | 62 ++++++++++++++++++++++++++ internal/validators/size_test.go | 58 ++++++++++++++++++++++++ internal/validators/validators.go | 2 + internal/validators/validators_test.go | 4 ++ 14 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 internal/validators/size.go create mode 100644 internal/validators/size_test.go diff --git a/.bigpicture.json b/.bigpicture.json index c0eff17..91b82a7 100644 --- a/.bigpicture.json +++ b/.bigpicture.json @@ -52,6 +52,13 @@ "*_test.go" ] } + }, + { + "type": "size", + "args": { + "module": "/internal", + "max": 49.9 + } } ] } \ No newline at end of file diff --git a/README.md b/README.md index ecd5c89..d16a824 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ BigPicture allows you to define these rules in a `.bigpicture.json` file and val - [FunctionValidator](#functionvalidator) - [InstabilityValidator](#instabilityvalidator) - [FileNameValidator](#filenamevalidator) + - [SizeValidator](#sizevalidator) - [Contribution](#contribution) # Installation @@ -203,6 +204,19 @@ Checks if the package has files which do not match the given regular expression. } ``` +### SizeValidator +Checks the percentage size of the packages under the given directory according to the lines of code. + +```json +{ + "type": "size", + "args": { + "module": "/internal", + "max": 25 + } +} +``` + # Contribution There are many ways in which you can participate in this project, for example: diff --git a/internal/browser/go.go b/internal/browser/go.go index 019d935..d6035a2 100644 --- a/internal/browser/go.go +++ b/internal/browser/go.go @@ -49,9 +49,11 @@ func (b *GoBrowser) browse(parentPath string, parentNode *graph.Node) { node := graph.NewNode(fName, path, graph.Dir, nil) b.tree.Nodes[node.Path] = node b.browse(path, node) + parentNode.LineCount += node.LineCount } else if strings.HasSuffix(fName, ".go") { node := b.parseFile(path, parentNode) b.tree.Nodes[node.Path] = node + parentNode.LineCount += node.LineCount } } } diff --git a/internal/browser/go_test.go b/internal/browser/go_test.go index 98b16ca..e604907 100644 --- a/internal/browser/go_test.go +++ b/internal/browser/go_test.go @@ -36,6 +36,8 @@ func TestGoBrowser_ParseFile(t *testing.T) { assert.Equal(t, "/internal/config", node.ImportRaw[1]) assert.Equal(t, "/internal/graph", node.ImportRaw[2]) assert.Equal(t, "/internal/server", node.ImportRaw[3]) + + assert.Equal(t, 110, node.LineCount) } func TestGoBrowser_Browse(t *testing.T) { @@ -55,6 +57,16 @@ func TestGoBrowser_browse(t *testing.T) { ).(*GoBrowser) browser.browse("./internal/browser", browser.tree.Root) - assert.Equal(t, 9, len(browser.tree.Nodes)) + + browser = NewBrowser( + LangGo, + graph.NewTree("root"), + []string{}, + "/", + ).(*GoBrowser) + + browser.browse("./internal/config", browser.tree.Root) + assert.Equal(t, 3, len(browser.tree.Nodes)) + assert.Equal(t, 94, browser.tree.Root.LineCount) } diff --git a/internal/browser/java.go b/internal/browser/java.go index 202a2e7..1e363ac 100644 --- a/internal/browser/java.go +++ b/internal/browser/java.go @@ -59,9 +59,11 @@ func (b *JavaBrowser) browse(parentPath string, parentNode *graph.Node) { node := graph.NewNode(fName, path, graph.Dir, nil) b.tree.Nodes[node.Path] = node b.browse(path, node) + parentNode.LineCount += node.LineCount } else if strings.HasSuffix(fName, ".java") { node := b.parseFile(path, parentNode) b.tree.Nodes[node.Path] = node + parentNode.LineCount += node.LineCount } } } diff --git a/internal/browser/java_test.go b/internal/browser/java_test.go index 6316221..b00714c 100644 --- a/internal/browser/java_test.go +++ b/internal/browser/java_test.go @@ -100,4 +100,5 @@ func TestJavaBrowser_browse(t *testing.T) { browser.browse("src/main/com/shashi/service", browser.tree.Root) assert.Equal(t, 8, len(browser.tree.Nodes)) + assert.Equal(t, browser.tree.Root.LineCount, 493) } diff --git a/internal/browser/python.go b/internal/browser/python.go index 78aea71..f01ffc5 100644 --- a/internal/browser/python.go +++ b/internal/browser/python.go @@ -59,9 +59,11 @@ func (b *PythonBrowser) browse(parentPath string, parentNode *graph.Node) { node := graph.NewNode(fName, path, graph.Dir, nil) b.tree.Nodes[node.Path] = node b.browse(path, node) + parentNode.LineCount += node.LineCount } else if strings.HasSuffix(fName, ".py") { node := b.parseFile(path, parentNode) b.tree.Nodes[node.Path] = node + parentNode.LineCount += node.LineCount } } } diff --git a/internal/browser/python_test.go b/internal/browser/python_test.go index bad228b..fc98247 100644 --- a/internal/browser/python_test.go +++ b/internal/browser/python_test.go @@ -101,4 +101,5 @@ func TestPythonBrowser_browse(t *testing.T) { browser.browse("base/", browser.tree.Root) assert.Equal(t, 6, len(browser.tree.Nodes)) + assert.Equal(t, browser.tree.Root.LineCount, 191) } diff --git a/internal/validators/linecount.go b/internal/validators/linecount.go index 8e7a63a..8d1e274 100644 --- a/internal/validators/linecount.go +++ b/internal/validators/linecount.go @@ -41,7 +41,7 @@ func NewLineCountValidator(args map[string]any, tree *graph.Tree) (*LineCountVal func (v *LineCountValidator) Validate() error { for _, node := range v.tree.Nodes { - if isIgnored(v.args.Ignore, node.Path) { + if isIgnored(v.args.Ignore, node.Path) || node.Type == graph.Dir { continue } diff --git a/internal/validators/linecount_test.go b/internal/validators/linecount_test.go index e10f278..d686d81 100644 --- a/internal/validators/linecount_test.go +++ b/internal/validators/linecount_test.go @@ -38,8 +38,8 @@ func TestNewLineCountValidator(t *testing.T) { func TestLineCountValidator_Validate(t *testing.T) { tree := graph.NewTree("root") - tree.Nodes["a"] = graph.NewNode("a", "a", graph.Dir, []string{}) - tree.Nodes["b"] = graph.NewNode("b", "b", graph.Dir, []string{}) + tree.Nodes["a"] = graph.NewNode("a", "a", graph.File, []string{}) + tree.Nodes["b"] = graph.NewNode("b", "b", graph.File, []string{}) tree.Nodes["a"].LineCount = 100 tree.Nodes["b"].LineCount = 200 @@ -58,12 +58,12 @@ func TestLineCountValidator_Validate(t *testing.T) { "browser/go", }) tree.Nodes["config"] = graph.NewNode("config", "config", graph.Dir, []string{}) - tree.Nodes["config/subconfig"] = graph.NewNode("subconfig", "config/subconfig", graph.Dir, []string{}) + tree.Nodes["config/subconfig"] = graph.NewNode("subconfig", "config/subconfig", graph.File, []string{}) tree.Nodes["config/subconfig"].LineCount = 200 - tree.Nodes["browser"] = graph.NewNode("browser", "browser", graph.Dir, []string{ + tree.Nodes["browser"] = graph.NewNode("browser", "browser", graph.File, []string{ "config/subconfig", }) - tree.Nodes["browser/go"] = graph.NewNode("go", "browser/go", graph.Dir, []string{ + tree.Nodes["browser/go"] = graph.NewNode("go", "browser/go", graph.File, []string{ "config/subconfig", }) diff --git a/internal/validators/size.go b/internal/validators/size.go new file mode 100644 index 0000000..a184be1 --- /dev/null +++ b/internal/validators/size.go @@ -0,0 +1,62 @@ +package validators + +import ( + "fmt" + "github.com/ismailbayram/bigpicture/internal/graph" + "strings" +) + +type SizeValidatorArgs struct { + Module string `json:"module" validate:"required=true"` + Max float64 `json:"max" validate:"required=true,min=1"` + Ignore []string `json:"ignore"` +} + +type SizeValidator struct { + args *SizeValidatorArgs + tree *graph.Tree +} + +func NewSizeValidator(args map[string]any, tree *graph.Tree) (*SizeValidator, error) { + validatorArgs := &SizeValidatorArgs{} + if err := validateArgs(args, validatorArgs); err != nil { + return nil, err + } + + if len(validatorArgs.Module) > 1 && strings.HasSuffix(validatorArgs.Module, "/*") { + validatorArgs.Module = validatorArgs.Module[:len(validatorArgs.Module)-2] + } + + module, err := validatePath(validatorArgs.Module, tree) + if err != nil { + return nil, err + } + validatorArgs.Module = module + + return &SizeValidator{ + args: validatorArgs, + tree: tree, + }, nil +} + +func (v *SizeValidator) Validate() error { + totalSize := v.tree.Nodes[v.args.Module].LineCount + + for _, node := range v.tree.Nodes { + if isIgnored(v.args.Ignore, node.Path) || node.Type == graph.File { + continue + } + + nodeSizePercent := float64(node.LineCount) / float64(totalSize) * 100 + + if node.Parent == v.args.Module && nodeSizePercent > v.args.Max { + return fmt.Errorf( + "Size of module '%s' is %.2f%%, but maximum allowed is %.2f%%", + node.Path, + nodeSizePercent, + v.args.Max, + ) + } + } + return nil +} diff --git a/internal/validators/size_test.go b/internal/validators/size_test.go new file mode 100644 index 0000000..e48e914 --- /dev/null +++ b/internal/validators/size_test.go @@ -0,0 +1,58 @@ +package validators + +import ( + "github.com/ismailbayram/bigpicture/internal/graph" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewSizeValidator(t *testing.T) { + tree := graph.NewTree("root") + tree.Nodes["a"] = graph.NewNode("a", "a", graph.Dir, []string{}) + tree.Nodes["b"] = graph.NewNode("b", "b", graph.Dir, []string{}) + tree.Nodes["a"].LineCount = 100 + tree.Nodes["b"].LineCount = 200 + + args := map[string]any{} + _, err := NewSizeValidator(args, nil) + assert.Equal(t, "module is required and must be string", err.Error()) + + args = map[string]any{"module": "a"} + _, err = NewSizeValidator(args, nil) + assert.Equal(t, "max is required and must be float64", err.Error()) + + args = map[string]any{"module": "a", "max": "wrong"} + _, err = NewSizeValidator(args, nil) + assert.Equal(t, "max is required and must be float64", err.Error()) + + args = map[string]any{"module": "wrong", "max": float64(100)} + _, err = NewSizeValidator(args, tree) + assert.Equal(t, "'wrong' is not a valid module. Path should start with /", err.Error()) + + args = map[string]any{"module": "a", "max": float64(100)} + validator, err := NewSizeValidator(args, tree) + assert.Nil(t, err) + assert.Equal(t, "a", validator.args.Module) + assert.Equal(t, float64(100), validator.args.Max) +} + +func TestSizeValidator_Validate(t *testing.T) { + tree := graph.NewTree("root") + tree.Nodes["."].LineCount = 600 + tree.Nodes["/dir1"] = graph.NewNode("dir1", "/dir1", graph.Dir, []string{}) + tree.Nodes["/dir1"].LineCount = 100 + tree.Nodes["/dir2"] = graph.NewNode("dir2", "/dir2", graph.Dir, []string{}) + tree.Nodes["/dir2"].LineCount = 200 + tree.Nodes["/dir3"] = graph.NewNode("dir3", "/dir3", graph.Dir, []string{}) + tree.Nodes["/dir3"].LineCount = 300 + + args := map[string]any{"module": ".", "max": float64(40)} + validator, _ := NewSizeValidator(args, tree) + err := validator.Validate() + assert.Equal(t, "Size of module '/dir3' is 50.00%, but maximum allowed is 40.00%", err.Error()) + + args = map[string]any{"module": ".", "max": float64(50)} + validator, _ = NewSizeValidator(args, tree) + err = validator.Validate() + assert.Nil(t, err) +} diff --git a/internal/validators/validators.go b/internal/validators/validators.go index fec1ede..8145c61 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -25,6 +25,8 @@ func NewValidator(t string, args map[string]any, tree *graph.Tree) (Validator, e return NewFunctionValidator(args, tree) case "file_name": return NewFileNameValidator(args, tree) + case "size": + return NewSizeValidator(args, tree) default: return nil, fmt.Errorf("unknown validator type: %s", t) } diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index 18fe043..f0f345c 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -31,6 +31,10 @@ func TestNewValidator(t *testing.T) { assert.Nil(t, err) assert.IsType(t, &FileNameValidator{}, validator) + validator, err = NewValidator("size", map[string]any{"module": "a", "max": float64(10)}, tree) + assert.Nil(t, err) + assert.IsType(t, &SizeValidator{}, validator) + validator, err = NewValidator("unknown", map[string]any{}, tree) assert.NotNil(t, err) assert.Nil(t, validator)