From d6d1f4d90c1086900ef22303357231a479b8d006 Mon Sep 17 00:00:00 2001 From: Shivasurya Date: Sun, 3 Nov 2024 22:11:04 -0500 Subject: [PATCH] feature: `IfStmt, DoStmt, WhileStmt, ForStmt` and Generic Statement support (#173) * :boat: added statement model * :boat: added statement model * added do while statement * added if stmt * :ship: added support query * :whale: added WhileStmt support * :rocket: supported do-while statement * :whale: added for loop statement support * :whale: add tests * :whale: add tests --- sourcecode-parser/graph/construct.go | 108 +++++++++++ sourcecode-parser/graph/construct_test.go | 20 ++- sourcecode-parser/graph/query.go | 44 +++++ sourcecode-parser/model/container.go | 118 ++++++++++++ sourcecode-parser/model/container_test.go | 146 +++++++++++++++ sourcecode-parser/model/location.go | 6 + sourcecode-parser/model/location_test.go | 54 ++++++ sourcecode-parser/model/member.go | 6 + sourcecode-parser/model/member_test.go | 28 +++ sourcecode-parser/model/module.go | 40 +++++ sourcecode-parser/model/module_test.go | 65 +++++++ sourcecode-parser/model/stmt.go | 168 ++++++++++++++++++ sourcecode-parser/model/stmt_test.go | 149 ++++++++++++++++ sourcecode-parser/model/top.go | 26 +++ sourcecode-parser/model/top_test.go | 127 +++++++++++++ .../com/ivb/udacity/movieDetailActivity.java | 12 ++ 16 files changed, 1114 insertions(+), 3 deletions(-) create mode 100644 sourcecode-parser/model/container.go create mode 100644 sourcecode-parser/model/container_test.go create mode 100644 sourcecode-parser/model/location.go create mode 100644 sourcecode-parser/model/location_test.go create mode 100644 sourcecode-parser/model/member.go create mode 100644 sourcecode-parser/model/member_test.go create mode 100644 sourcecode-parser/model/module.go create mode 100644 sourcecode-parser/model/module_test.go create mode 100644 sourcecode-parser/model/stmt.go create mode 100644 sourcecode-parser/model/stmt_test.go create mode 100644 sourcecode-parser/model/top.go create mode 100644 sourcecode-parser/model/top_test.go diff --git a/sourcecode-parser/graph/construct.go b/sourcecode-parser/graph/construct.go index 41b8b7c..449a2e8 100644 --- a/sourcecode-parser/graph/construct.go +++ b/sourcecode-parser/graph/construct.go @@ -44,6 +44,10 @@ type Node struct { JavaDoc *model.Javadoc BinaryExpr *model.BinaryExpr ClassInstanceExpr *model.ClassInstanceExpr + IfStmt *model.IfStmt + WhileStmt *model.WhileStmt + DoStmt *model.DoStmt + ForStmt *model.ForStmt } type Edge struct { @@ -172,6 +176,110 @@ func parseJavadocTags(commentContent string) *model.Javadoc { func buildGraphFromAST(node *sitter.Node, sourceCode []byte, graph *CodeGraph, currentContext *Node, file string) { isJavaSourceFile := isJavaSourceFile(file) switch node.Type() { + case "if_statement": + ifNode := model.IfStmt{} + // get the condition of the if statement + conditionNode := node.Child(1) + if conditionNode != nil { + ifNode.Condition = &model.Expr{Node: *conditionNode, NodeString: conditionNode.Content(sourceCode)} + } + // get the then block of the if statement + thenNode := node.Child(2) + if thenNode != nil { + ifNode.Then = model.Stmt{NodeString: thenNode.Content(sourceCode)} + } + // get the else block of the if statement + elseNode := node.Child(4) + if elseNode != nil { + ifNode.Else = model.Stmt{NodeString: elseNode.Content(sourceCode)} + } + + methodID := fmt.Sprintf("ifstmt_%d_%d_%s", node.StartPoint().Row+1, node.StartPoint().Column+1, file) + // add node to graph + ifStmtNode := &Node{ + ID: GenerateSha256(methodID), + Type: "IfStmt", + Name: "IfStmt", + IsExternal: true, + CodeSnippet: node.Content(sourceCode), + LineNumber: node.StartPoint().Row + 1, + File: file, + isJavaSourceFile: isJavaSourceFile, + IfStmt: &ifNode, + } + graph.AddNode(ifStmtNode) + case "while_statement": + whileNode := model.WhileStmt{} + // get the condition of the while statement + conditionNode := node.Child(1) + if conditionNode != nil { + whileNode.Condition = &model.Expr{Node: *conditionNode, NodeString: conditionNode.Content(sourceCode)} + } + methodID := fmt.Sprintf("while_stmt_%d_%d_%s", node.StartPoint().Row+1, node.StartPoint().Column+1, file) + // add node to graph + whileStmtNode := &Node{ + ID: GenerateSha256(methodID), + Type: "WhileStmt", + Name: "WhileStmt", + IsExternal: true, + CodeSnippet: node.Content(sourceCode), + LineNumber: node.StartPoint().Row + 1, + File: file, + isJavaSourceFile: isJavaSourceFile, + WhileStmt: &whileNode, + } + graph.AddNode(whileStmtNode) + case "do_statement": + doWhileNode := model.DoStmt{} + // get the condition of the while statement + conditionNode := node.Child(2) + if conditionNode != nil { + doWhileNode.Condition = &model.Expr{Node: *conditionNode, NodeString: conditionNode.Content(sourceCode)} + } + methodID := fmt.Sprintf("dowhile_stmt_%d_%d_%s", node.StartPoint().Row+1, node.StartPoint().Column+1, file) + // add node to graph + doWhileStmtNode := &Node{ + ID: GenerateSha256(methodID), + Type: "DoStmt", + Name: "DoStmt", + IsExternal: true, + CodeSnippet: node.Content(sourceCode), + LineNumber: node.StartPoint().Row + 1, + File: file, + isJavaSourceFile: isJavaSourceFile, + DoStmt: &doWhileNode, + } + graph.AddNode(doWhileStmtNode) + case "for_statement": + forNode := model.ForStmt{} + // get the condition of the while statement + initNode := node.ChildByFieldName("init") + if initNode != nil { + forNode.Init = &model.Expr{Node: *initNode, NodeString: initNode.Content(sourceCode)} + } + conditionNode := node.ChildByFieldName("condition") + if conditionNode != nil { + forNode.Condition = &model.Expr{Node: *conditionNode, NodeString: conditionNode.Content(sourceCode)} + } + incrementNode := node.ChildByFieldName("increment") + if incrementNode != nil { + forNode.Increment = &model.Expr{Node: *incrementNode, NodeString: incrementNode.Content(sourceCode)} + } + + methodID := fmt.Sprintf("for_stmt_%d_%d_%s", node.StartPoint().Row+1, node.StartPoint().Column+1, file) + // add node to graph + forStmtNode := &Node{ + ID: GenerateSha256(methodID), + Type: "ForStmt", + Name: "ForStmt", + IsExternal: true, + CodeSnippet: node.Content(sourceCode), + LineNumber: node.StartPoint().Row + 1, + File: file, + isJavaSourceFile: isJavaSourceFile, + ForStmt: &forNode, + } + graph.AddNode(forStmtNode) case "binary_expression": leftNode := node.ChildByFieldName("left") rightNode := node.ChildByFieldName("right") diff --git a/sourcecode-parser/graph/construct_test.go b/sourcecode-parser/graph/construct_test.go index be1266c..ae1f350 100644 --- a/sourcecode-parser/graph/construct_test.go +++ b/sourcecode-parser/graph/construct_test.go @@ -753,13 +753,27 @@ func TestBuildGraphFromAST(t *testing.T) { int i = 1 | 1; int j = 1 ^ 1; int l = 1 >>> 1; + while (a > 0) { + a--; + } + for (int i = 0; i < 10; i++) { + System.out.println(i); + } + do { + System.out.println("Hello, World!"); + } while (a > 0); + if (a < 0) { + System.out.println("Negative number"); + } else { + System.out.println("Positive number"); + } return (5 > 3) && (10 <= 20) || (15 != 12) || (20 > 15); } } `, - expectedNodes: 49, - expectedEdges: 0, - expectedTypes: []string{"class_declaration", "method_declaration", "binary_expression", "comp_expression", "and_expression", "or_expression"}, + expectedNodes: 63, + expectedEdges: 4, + expectedTypes: []string{"class_declaration", "method_declaration", "binary_expression", "comp_expression", "and_expression", "or_expression", "IfStmt", "ForStmt", "WhileStmt", "DoStmt"}, unexpectedTypes: []string{""}, }, { diff --git a/sourcecode-parser/graph/query.go b/sourcecode-parser/graph/query.go index be02cda..6fd409c 100644 --- a/sourcecode-parser/graph/query.go +++ b/sourcecode-parser/graph/query.go @@ -119,6 +119,22 @@ func (env *Env) GetClassInstanceExprName() string { return env.Node.ClassInstanceExpr.ClassName } +func (env *Env) GetIfStmt() *model.IfStmt { + return env.Node.IfStmt +} + +func (env *Env) GetWhileStmt() *model.WhileStmt { + return env.Node.WhileStmt +} + +func (env *Env) GetDoStmt() *model.DoStmt { + return env.Node.DoStmt +} + +func (env *Env) GetForStmt() *model.ForStmt { + return env.Node.ForStmt +} + func QueryEntities(graph *CodeGraph, query parser.Query) (nodes [][]*Node, output [][]interface{}) { result := make([][]*Node, 0) @@ -290,6 +306,10 @@ func generateProxyEnv(node *Node, query parser.Query) map[string]interface{} { unsignedRightShiftExpression := "unsigned_right_shift_expression" xorBitwsieExpression := "xor_bitwise_expression" classInstanceExpression := "ClassInstanceExpr" + ifStmt := "IfStmt" + whileStmt := "WhileStmt" + doStmt := "DoStmt" + forStmt := "ForStmt" // print query select list for _, entity := range query.SelectList { @@ -338,6 +358,14 @@ func generateProxyEnv(node *Node, query parser.Query) map[string]interface{} { xorBitwsieExpression = entity.Alias case "ClassInstanceExpr": classInstanceExpression = entity.Alias + case "IfStmt": + ifStmt = entity.Alias + case "WhileStmt": + whileStmt = entity.Alias + case "DoStmt": + doStmt = entity.Alias + case "ForStmt": + forStmt = entity.Alias } } env := map[string]interface{}{ @@ -468,6 +496,22 @@ func generateProxyEnv(node *Node, query parser.Query) map[string]interface{} { "toString": proxyenv.ToString, "getClassInstanceExpr": proxyenv.GetClassInstanceExpr, }, + ifStmt: map[string]interface{}{ + "getIfStmt": proxyenv.GetIfStmt, + "toString": proxyenv.ToString, + }, + whileStmt: map[string]interface{}{ + "getWhileStmt": proxyenv.GetWhileStmt, + "toString": proxyenv.ToString, + }, + doStmt: map[string]interface{}{ + "getDoStmt": proxyenv.GetDoStmt, + "toString": proxyenv.ToString, + }, + forStmt: map[string]interface{}{ + "getForStmt": proxyenv.GetForStmt, + "toString": proxyenv.ToString, + }, } return env } diff --git a/sourcecode-parser/model/container.go b/sourcecode-parser/model/container.go new file mode 100644 index 0000000..0f8d612 --- /dev/null +++ b/sourcecode-parser/model/container.go @@ -0,0 +1,118 @@ +package model + +type Container struct { + Top +} + +func (c *Container) ToString() string { + return "" +} + +type File struct { + Container + File string +} + +func (f *File) IsSourceFile() bool { + // check if file extension is .java + if f.File[len(f.File)-5:] == ".java" || f.File[len(f.File)-4:] == ".kt" { + return true + } + return false +} + +func (f *File) IsJavaSourceFile() bool { + return f.File[len(f.File)-5:] == ".java" +} + +func (f *File) IsKotlinSourceFile() bool { + return f.File[len(f.File)-5:] == ".kt" +} + +func (f *File) GetAPrimaryQlClass() string { + return "File" +} + +type CompilationUnit struct { + File + module Module + CuPackage Package + Name string +} + +func (c *CompilationUnit) GetAPrimaryQlClass() string { + return "CompilationUnit" +} + +func (c *CompilationUnit) GetModule() Module { + return c.module +} + +func (c *CompilationUnit) GetName() string { + return c.Name +} + +func (c *CompilationUnit) GetPackage() Package { + return c.CuPackage +} + +func (c *CompilationUnit) HasName(name string) bool { + return c.Name == name +} + +func (c *CompilationUnit) ToString() string { + return c.Name +} + +type JarFile struct { + File + JarFile string + ImplementationVersion string + ManifestEntryAttributes map[string]map[string]string + ManifestMainAttributes map[string]string + SpecificationVersion string +} + +func (j *JarFile) GetAPrimaryQlClass() string { + return "JarFile" +} + +func (j *JarFile) GetJarFile() string { + return j.JarFile +} + +func (j *JarFile) GetImplementationVersion() string { + return j.ImplementationVersion +} + +func (j *JarFile) GetManifestEntryAttributes(entry, key string) (string, bool) { + if attributes, exists := j.ManifestEntryAttributes[entry]; exists { + if value, keyExists := attributes[key]; keyExists { + return value, true + } + } + return "", false +} + +func (j *JarFile) GetManifestMainAttributes(key string) (string, bool) { + if value, exists := j.ManifestMainAttributes[key]; exists { + return value, true + } + return "", false +} + +func (j *JarFile) GetSpecificationVersion() string { + return j.SpecificationVersion +} + +type Package struct { + Package string +} + +func (p *Package) GetAPrimaryQlClass() string { + return "Package" +} + +func (p *Package) GetURL() string { + return p.Package +} diff --git a/sourcecode-parser/model/container_test.go b/sourcecode-parser/model/container_test.go new file mode 100644 index 0000000..117f17c --- /dev/null +++ b/sourcecode-parser/model/container_test.go @@ -0,0 +1,146 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFile_IsSourceFile(t *testing.T) { + tests := []struct { + name string + file File + expected bool + }{ + { + name: "Java source file", + file: File{File: "Test.java"}, + expected: true, + }, + { + name: "Non-source file", + file: File{File: "Test.txt"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.file.IsSourceFile() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestJarFile_GetManifestEntryAttributes(t *testing.T) { + jarFile := &JarFile{ + ManifestEntryAttributes: map[string]map[string]string{ + "entry1": { + "key1": "value1", + "key2": "value2", + }, + }, + } + + t.Run("Existing entry and key", func(t *testing.T) { + value, exists := jarFile.GetManifestEntryAttributes("entry1", "key1") + assert.True(t, exists) + assert.Equal(t, "value1", value) + }) + + t.Run("Existing entry, non-existing key", func(t *testing.T) { + value, exists := jarFile.GetManifestEntryAttributes("entry1", "nonexistent") + assert.False(t, exists) + assert.Equal(t, "", value) + }) + + t.Run("Non-existing entry", func(t *testing.T) { + value, exists := jarFile.GetManifestEntryAttributes("nonexistent", "key1") + assert.False(t, exists) + assert.Equal(t, "", value) + }) +} + +func TestJarFile_GetManifestMainAttributes(t *testing.T) { + jarFile := &JarFile{ + ManifestMainAttributes: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + } + + t.Run("Existing key", func(t *testing.T) { + value, exists := jarFile.GetManifestMainAttributes("key1") + assert.True(t, exists) + assert.Equal(t, "value1", value) + }) + + t.Run("Non-existing key", func(t *testing.T) { + value, exists := jarFile.GetManifestMainAttributes("nonexistent") + assert.False(t, exists) + assert.Equal(t, "", value) + }) +} + +func TestCompilationUnit_HasName(t *testing.T) { + cu := &CompilationUnit{Name: "TestClass"} + + t.Run("Matching name", func(t *testing.T) { + assert.True(t, cu.HasName("TestClass")) + }) + + t.Run("Non-matching name", func(t *testing.T) { + assert.False(t, cu.HasName("OtherClass")) + }) + + t.Run("Empty name", func(t *testing.T) { + assert.False(t, cu.HasName("")) + }) +} + +func TestFile_IsJavaSourceFile(t *testing.T) { + tests := []struct { + name string + file File + expected bool + }{ + { + name: "Java file", + file: File{File: "Test.java"}, + expected: true, + }, + { + name: "Non-Java file", + file: File{File: "Test.kt"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.file.IsJavaSourceFile() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestFile_IsKotlinSourceFile(t *testing.T) { + tests := []struct { + name string + file File + expected bool + }{ + { + name: "Non-Kotlin file", + file: File{File: "Test.java"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.file.IsKotlinSourceFile() + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/sourcecode-parser/model/location.go b/sourcecode-parser/model/location.go new file mode 100644 index 0000000..25efe3b --- /dev/null +++ b/sourcecode-parser/model/location.go @@ -0,0 +1,6 @@ +package model + +type Location struct { + File string + Line int +} diff --git a/sourcecode-parser/model/location_test.go b/sourcecode-parser/model/location_test.go new file mode 100644 index 0000000..d6e3e89 --- /dev/null +++ b/sourcecode-parser/model/location_test.go @@ -0,0 +1,54 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocation(t *testing.T) { + t.Run("New location with valid values", func(t *testing.T) { + loc := Location{ + File: "test.go", + Line: 42, + } + assert.Equal(t, "test.go", loc.File) + assert.Equal(t, 42, loc.Line) + }) + + t.Run("New location with empty file", func(t *testing.T) { + loc := Location{ + File: "", + Line: 1, + } + assert.Empty(t, loc.File) + assert.Equal(t, 1, loc.Line) + }) + + t.Run("New location with zero line", func(t *testing.T) { + loc := Location{ + File: "main.go", + Line: 0, + } + assert.Equal(t, "main.go", loc.File) + assert.Zero(t, loc.Line) + }) + + t.Run("New location with negative line", func(t *testing.T) { + loc := Location{ + File: "src.go", + Line: -1, + } + assert.Equal(t, "src.go", loc.File) + assert.Equal(t, -1, loc.Line) + }) + + t.Run("New location with file path", func(t *testing.T) { + loc := Location{ + File: "/path/to/file.go", + Line: 100, + } + assert.Equal(t, "/path/to/file.go", loc.File) + assert.Equal(t, 100, loc.Line) + }) +} diff --git a/sourcecode-parser/model/member.go b/sourcecode-parser/model/member.go new file mode 100644 index 0000000..609ceb3 --- /dev/null +++ b/sourcecode-parser/model/member.go @@ -0,0 +1,6 @@ +package model + +type Callable struct { + StmtParent + CallableName string +} diff --git a/sourcecode-parser/model/member_test.go b/sourcecode-parser/model/member_test.go new file mode 100644 index 0000000..45d8fe1 --- /dev/null +++ b/sourcecode-parser/model/member_test.go @@ -0,0 +1,28 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCallable(t *testing.T) { + t.Run("New Callable with name", func(t *testing.T) { + callable := &Callable{ + CallableName: "testFunction", + } + assert.Equal(t, "testFunction", callable.CallableName) + }) + + t.Run("Empty Callable name", func(t *testing.T) { + callable := &Callable{} + assert.Equal(t, "", callable.CallableName) + }) + + t.Run("Callable with special characters", func(t *testing.T) { + callable := &Callable{ + CallableName: "test$Function_123", + } + assert.Equal(t, "test$Function_123", callable.CallableName) + }) +} diff --git a/sourcecode-parser/model/module.go b/sourcecode-parser/model/module.go new file mode 100644 index 0000000..561f159 --- /dev/null +++ b/sourcecode-parser/model/module.go @@ -0,0 +1,40 @@ +package model + +type Module struct { + Cu *CompilationUnit + Di Directive + Name string + isOpen bool +} + +func (m *Module) GetAPrimaryQlClass() string { + return "Module" +} + +func (m *Module) GetACompilationUnit() *CompilationUnit { + return m.Cu +} + +func (m *Module) GetName() string { + return m.Name +} + +func (m *Module) ToString() string { + return m.Name +} + +func (m *Module) GetDirective() *Directive { + return &m.Di +} + +func (m *Module) IsOpen() bool { + return m.isOpen +} + +type Directive struct { + Directive string +} + +func (d *Directive) ToString() string { + return d.Directive +} diff --git a/sourcecode-parser/model/module_test.go b/sourcecode-parser/model/module_test.go new file mode 100644 index 0000000..cf8e883 --- /dev/null +++ b/sourcecode-parser/model/module_test.go @@ -0,0 +1,65 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestModule(t *testing.T) { + cu := &CompilationUnit{} + directive := Directive{Directive: "requires java.base"} + module := &Module{ + Cu: cu, + Di: directive, + Name: "com.example.module", + isOpen: true, + } + + t.Run("GetAPrimaryQlClass", func(t *testing.T) { + assert.Equal(t, "Module", module.GetAPrimaryQlClass()) + }) + + t.Run("GetACompilationUnit", func(t *testing.T) { + assert.Equal(t, cu, module.GetACompilationUnit()) + }) + + t.Run("GetName", func(t *testing.T) { + assert.Equal(t, "com.example.module", module.GetName()) + }) + + t.Run("ToString", func(t *testing.T) { + assert.Equal(t, "com.example.module", module.ToString()) + }) + + t.Run("GetDirective", func(t *testing.T) { + assert.Equal(t, &directive, module.GetDirective()) + }) + + t.Run("IsOpen", func(t *testing.T) { + assert.True(t, module.IsOpen()) + }) +} + +func TestModuleWithEmptyValues(t *testing.T) { + module := &Module{} + + t.Run("Empty module properties", func(t *testing.T) { + assert.Nil(t, module.GetACompilationUnit()) + assert.Empty(t, module.GetName()) + assert.Empty(t, module.ToString()) + assert.False(t, module.IsOpen()) + }) +} + +func TestDirective(t *testing.T) { + t.Run("ToString with value", func(t *testing.T) { + directive := &Directive{Directive: "requires transitive java.sql"} + assert.Equal(t, "requires transitive java.sql", directive.ToString()) + }) + + t.Run("ToString empty", func(t *testing.T) { + directive := &Directive{} + assert.Empty(t, directive.ToString()) + }) +} diff --git a/sourcecode-parser/model/stmt.go b/sourcecode-parser/model/stmt.go new file mode 100644 index 0000000..fddae4f --- /dev/null +++ b/sourcecode-parser/model/stmt.go @@ -0,0 +1,168 @@ +package model + +import "fmt" + +type StmtParent struct { + Top +} + +type IStmt interface { + GetAChildStmt() *Stmt + GetBasicBlock() *BasicBlock + GetCompilationUnit() *CompilationUnit + GetControlFlowNode() *ControlFlowNode + GetEnclosingCallable() *Callable + GetEnclosingStmt() *Stmt + GetHalsteadID() int + GetIndex() int + GetMetrics() interface{} + GetParent() *Stmt + IsNthChildOf(parent *Stmt, n int) bool + Pp() string + ToString() string +} + +type Stmt struct { + NodeString string + StmtParent +} + +type IConditionalStmt interface { + GetCondition() *Expr +} + +type ConditionalStmt struct { + Stmt + Condition *Expr +} + +type IIfStmt interface { + GetCondition() *Expr + GetElse() *Stmt + GetThen() *Stmt + GetHalsteadID() int + GetAPrimaryQlClass() string + GetPP() string + ToString() string +} + +type IfStmt struct { + ConditionalStmt + Else Stmt + Then Stmt +} + +func (ifStmt *IfStmt) GetCondition() *Expr { + return ifStmt.Condition +} + +func (ifStmt *IfStmt) GetElse() *Stmt { + return &ifStmt.Else +} + +func (ifStmt *IfStmt) GetThen() *Stmt { + return &ifStmt.Then +} + +func (ifStmt *IfStmt) GetAPrimaryQlClass() string { + return "ifStmt" +} + +func (ifStmt *IfStmt) GetPP() string { + return fmt.Sprintf("if (%s) %s else %s", ifStmt.Condition.NodeString, ifStmt.Then.NodeString, ifStmt.Else.NodeString) +} + +func (ifStmt *IfStmt) ToString() string { + return fmt.Sprintf("if (%s) %s else %s", ifStmt.Condition.NodeString, ifStmt.Then.NodeString, ifStmt.Else.NodeString) +} + +type DoStmt struct { + ConditionalStmt +} + +type IDoStmt interface { + GetAPrimaryQlClass() string + GetCondition() *Expr + GetHalsteadID() int + GetStmt() *Stmt + GetPP() string + ToString() string +} + +type IForStmt interface { + GetPrimaryQlClass() string + GetAnInit() *Expr + GetAnIterationVariable() *Expr + GetAnUpdate() *Expr + GetCondition() *Expr + GetHalsteadID() int + GetInit(int) *Expr + GetStmt() *Stmt + GetUpdate(int) *Expr + GetPP() string + ToString() string +} + +type ForStmt struct { + ConditionalStmt + Init *Expr + Increment *Expr +} + +func (forStmt *ForStmt) GetPrimaryQlClass() string { + return "ForStmt" +} + +func (forStmt *ForStmt) GetAnInit() *Expr { + return forStmt.Init +} + +func (forStmt *ForStmt) GetCondition() *Expr { + return forStmt.Condition +} + +func (forStmt *ForStmt) GetAnUpdate() *Expr { + return forStmt.Increment +} + +func (forStmt *ForStmt) ToString() string { + return fmt.Sprintf("for (%s; %s; %s) %s", forStmt.Init.NodeString, forStmt.Condition.NodeString, forStmt.Increment.NodeString, forStmt.Stmt.NodeString) +} + +type IWhileStmt interface { + GetAPrimaryQlClass() string + GetCondition() *Expr + GetHalsteadID() int + GetStmt() Stmt + GetPP() string + ToString() string +} + +type WhileStmt struct { + ConditionalStmt +} + +func (whileStmt *WhileStmt) GetAPrimaryQlClass() string { + return "WhileStmt" +} + +func (whileStmt *WhileStmt) GetCondition() *Expr { + return whileStmt.Condition +} + +func (whileStmt *WhileStmt) GetHalsteadID() int { + // TODO: Implement Halstead ID calculation for WhileStmt + return 0 +} + +func (whileStmt *WhileStmt) GetStmt() Stmt { + return whileStmt.Stmt +} + +func (whileStmt *WhileStmt) GetPP() string { + return fmt.Sprintf("while (%s) %s", whileStmt.Condition.NodeString, whileStmt.Stmt.NodeString) +} + +func (whileStmt *WhileStmt) ToString() string { + return fmt.Sprintf("while (%s) %s", whileStmt.Condition.NodeString, whileStmt.Stmt.NodeString) +} diff --git a/sourcecode-parser/model/stmt_test.go b/sourcecode-parser/model/stmt_test.go new file mode 100644 index 0000000..055fa9a --- /dev/null +++ b/sourcecode-parser/model/stmt_test.go @@ -0,0 +1,149 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWhileStmt(t *testing.T) { + t.Run("GetAPrimaryQlClass", func(t *testing.T) { + whileStmt := &WhileStmt{} + assert.Equal(t, "WhileStmt", whileStmt.GetAPrimaryQlClass()) + }) + + t.Run("GetCondition", func(t *testing.T) { + condition := &Expr{NodeString: "x < 10"} + whileStmt := &WhileStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: condition, + }, + } + assert.Equal(t, condition, whileStmt.GetCondition()) + }) + + t.Run("GetHalsteadID", func(t *testing.T) { + whileStmt := &WhileStmt{} + assert.Equal(t, 0, whileStmt.GetHalsteadID()) + }) + + t.Run("GetStmt", func(t *testing.T) { + stmt := Stmt{NodeString: "x++"} + whileStmt := &WhileStmt{ + ConditionalStmt: ConditionalStmt{ + Stmt: stmt, + }, + } + assert.Equal(t, stmt, whileStmt.GetStmt()) + }) + + t.Run("GetPP", func(t *testing.T) { + whileStmt := &WhileStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: &Expr{NodeString: "x < 10"}, + Stmt: Stmt{NodeString: "x++"}, + }, + } + expected := "while (x < 10) x++" + assert.Equal(t, expected, whileStmt.GetPP()) + }) + + t.Run("ToString", func(t *testing.T) { + whileStmt := &WhileStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: &Expr{NodeString: "x < 10"}, + Stmt: Stmt{NodeString: "x++"}, + }, + } + expected := "while (x < 10) x++" + assert.Equal(t, expected, whileStmt.ToString()) + }) +} + +func TestForStmt(t *testing.T) { + t.Run("GetPrimaryQlClass", func(t *testing.T) { + forStmt := &ForStmt{} + assert.Equal(t, "ForStmt", forStmt.GetPrimaryQlClass()) + }) + + t.Run("GetAnInit", func(t *testing.T) { + init := &Expr{NodeString: "int i = 0"} + forStmt := &ForStmt{Init: init} + assert.Equal(t, init, forStmt.GetAnInit()) + }) + + t.Run("GetCondition", func(t *testing.T) { + condition := &Expr{NodeString: "i < 10"} + forStmt := &ForStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: condition, + }, + } + assert.Equal(t, condition, forStmt.GetCondition()) + }) + + t.Run("GetAnUpdate", func(t *testing.T) { + increment := &Expr{NodeString: "i++"} + forStmt := &ForStmt{Increment: increment} + assert.Equal(t, increment, forStmt.GetAnUpdate()) + }) + + t.Run("ToString", func(t *testing.T) { + forStmt := &ForStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: &Expr{NodeString: "i < 10"}, + Stmt: Stmt{NodeString: "System.out.println(i)"}, + }, + Init: &Expr{NodeString: "int i = 0"}, + Increment: &Expr{NodeString: "i++"}, + } + expected := "for (int i = 0; i < 10; i++) System.out.println(i)" + assert.Equal(t, expected, forStmt.ToString()) + }) +} + +func TestIfStmt(t *testing.T) { + t.Run("GetCondition", func(t *testing.T) { + condition := &Expr{NodeString: "x > 0"} + ifStmt := &IfStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: condition, + }, + } + assert.Equal(t, condition, ifStmt.GetCondition()) + }) + + t.Run("GetElse", func(t *testing.T) { + elseStmt := Stmt{NodeString: "return false"} + ifStmt := &IfStmt{ + Else: elseStmt, + } + assert.Equal(t, &elseStmt, ifStmt.GetElse()) + }) + + t.Run("GetThen", func(t *testing.T) { + thenStmt := Stmt{NodeString: "return true"} + ifStmt := &IfStmt{ + Then: thenStmt, + } + assert.Equal(t, &thenStmt, ifStmt.GetThen()) + }) + + t.Run("GetAPrimaryQlClass", func(t *testing.T) { + ifStmt := &IfStmt{} + assert.Equal(t, "ifStmt", ifStmt.GetAPrimaryQlClass()) + }) + + t.Run("GetPP and ToString", func(t *testing.T) { + ifStmt := &IfStmt{ + ConditionalStmt: ConditionalStmt{ + Condition: &Expr{NodeString: "x > 0"}, + }, + Then: Stmt{NodeString: "return true"}, + Else: Stmt{NodeString: "return false"}, + } + expected := "if (x > 0) return true else return false" + assert.Equal(t, expected, ifStmt.GetPP()) + assert.Equal(t, expected, ifStmt.ToString()) + }) +} diff --git a/sourcecode-parser/model/top.go b/sourcecode-parser/model/top.go new file mode 100644 index 0000000..cf7399e --- /dev/null +++ b/sourcecode-parser/model/top.go @@ -0,0 +1,26 @@ +package model + +type BaseTop interface { + GetAPrimaryQlClass() string + GetFile() string + GetLocation() Location + GetNumberOfCommentLines() int + GetNumberOfLinesOfCode() int + GetPrimaryQlClasses() string + GetTotalNumberOfLines() int + HasLocationInfo(filepath string, startline, startcolumn, endline, endcolumn int) bool + ToString() string +} + +type Top struct { + BaseTop + File string +} + +type ControlFlowNode struct { + Top +} + +type BasicBlock struct { + ControlFlowNode +} diff --git a/sourcecode-parser/model/top_test.go b/sourcecode-parser/model/top_test.go new file mode 100644 index 0000000..1e5756f --- /dev/null +++ b/sourcecode-parser/model/top_test.go @@ -0,0 +1,127 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTop(t *testing.T) { + top := &Top{ + File: "test.go", + } + + assert.Equal(t, "test.go", top.File) +} + +func TestControlFlowNode(t *testing.T) { + node := &ControlFlowNode{ + Top: Top{ + File: "test.go", + }, + } + + assert.Equal(t, "test.go", node.File) +} + +func TestBasicBlock(t *testing.T) { + block := &BasicBlock{ + ControlFlowNode: ControlFlowNode{ + Top: Top{ + File: "test.go", + }, + }, + } + + assert.Equal(t, "test.go", block.File) +} + +type MockTop struct { + BaseTop + file string + location Location + numberOfCommentLines int + numberOfLinesOfCode int + primaryQlClasses string + totalNumberOfLines int + hasLocationInfoResult bool +} + +func (m *MockTop) GetAPrimaryQlClass() string { + return "MockClass" +} + +func (m *MockTop) GetFile() string { + return m.file +} + +func (m *MockTop) GetLocation() Location { + return m.location +} + +func (m *MockTop) GetNumberOfCommentLines() int { + return m.numberOfCommentLines +} + +func (m *MockTop) GetNumberOfLinesOfCode() int { + return m.numberOfLinesOfCode +} + +func (m *MockTop) GetPrimaryQlClasses() string { + return m.primaryQlClasses +} + +func (m *MockTop) GetTotalNumberOfLines() int { + return m.totalNumberOfLines +} + +func (m *MockTop) HasLocationInfo(filepath string, startline, startcolumn, endline, endcolumn int) bool { + return m.hasLocationInfoResult +} + +func (m *MockTop) ToString() string { + return "MockTop" +} + +func TestBaseTopInterface(t *testing.T) { + mock := &MockTop{ + file: "test.go", + numberOfCommentLines: 10, + numberOfLinesOfCode: 50, + primaryQlClasses: "TestClass", + totalNumberOfLines: 60, + hasLocationInfoResult: true, + } + + t.Run("GetAPrimaryQlClass", func(t *testing.T) { + assert.Equal(t, "MockClass", mock.GetAPrimaryQlClass()) + }) + + t.Run("GetFile", func(t *testing.T) { + assert.Equal(t, "test.go", mock.GetFile()) + }) + + t.Run("GetNumberOfCommentLines", func(t *testing.T) { + assert.Equal(t, 10, mock.GetNumberOfCommentLines()) + }) + + t.Run("GetNumberOfLinesOfCode", func(t *testing.T) { + assert.Equal(t, 50, mock.GetNumberOfLinesOfCode()) + }) + + t.Run("GetPrimaryQlClasses", func(t *testing.T) { + assert.Equal(t, "TestClass", mock.GetPrimaryQlClasses()) + }) + + t.Run("GetTotalNumberOfLines", func(t *testing.T) { + assert.Equal(t, 60, mock.GetTotalNumberOfLines()) + }) + + t.Run("HasLocationInfo", func(t *testing.T) { + assert.True(t, mock.HasLocationInfo("test.go", 1, 1, 10, 10)) + }) + + t.Run("ToString", func(t *testing.T) { + assert.Equal(t, "MockTop", mock.ToString()) + }) +} diff --git a/test-src/android/app/src/main/java/com/ivb/udacity/movieDetailActivity.java b/test-src/android/app/src/main/java/com/ivb/udacity/movieDetailActivity.java index e4035cc..3d8763b 100644 --- a/test-src/android/app/src/main/java/com/ivb/udacity/movieDetailActivity.java +++ b/test-src/android/app/src/main/java/com/ivb/udacity/movieDetailActivity.java @@ -73,6 +73,18 @@ public boolean onOptionsItemSelected(MenuItem item) { onBackPressed(); return true; } + while (true) { + int i = 0; + i++; + } + + do { + i++; + } while (i < 10); + + for (int i = 0; i < 10; i++) { + i++; + } Cipher.getInstance("RC4") MessageDigest.getInstance("SHA1", "BC"); return super.onOptionsItemSelected(item);