From 320ca9951bd77ecdc18d6af044d61dbf852279ec Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 4 Nov 2019 22:34:51 +0800 Subject: [PATCH] feat: add a basic bad smell examples --- bs/BadSmellApp.go | 84 +++++++++++++++++ bs/BadSmellListener.go | 200 +++++++++++++++++++++++++++++++++++++++++ bs/bs.go | 32 ------- cmd/badsmell.go | 21 +++-- 4 files changed, 298 insertions(+), 39 deletions(-) create mode 100644 bs/BadSmellApp.go create mode 100644 bs/BadSmellListener.go delete mode 100644 bs/bs.go diff --git a/bs/BadSmellApp.go b/bs/BadSmellApp.go new file mode 100644 index 0000000..7be20a1 --- /dev/null +++ b/bs/BadSmellApp.go @@ -0,0 +1,84 @@ +package call + +import ( + "fmt" + "github.com/antlr/antlr4/runtime/Go/antlr" + "os" + "path/filepath" + "strings" + + . "github.com/phodal/coca/adapter/models" + . "github.com/phodal/coca/language/java" +) + +var nodeInfos []JClassNode +type BadSmellModel struct { + File string + Line string + Bs string +} + +type BadSmellApp struct { + +} + +func (j *BadSmellApp) AnalysisPath(codeDir string) []BadSmellModel { + nodeInfos = nil + files := (*BadSmellApp)(nil).javaFiles(codeDir) + for index := range files { + nodeInfo := NewClassNode() + file := files[index] + + displayName := filepath.Base(file) + fmt.Println("Start parse java call: " + displayName) + + parser := (*BadSmellApp)(nil).processFile(file) + context := parser.CompilationUnit() + + listener := NewBadSmellListener() + + antlr.NewParseTreeWalker().Walk(listener, context) + + nodeInfo = listener.getNodeInfo() + nodeInfo.Path = file + nodeInfos = append(nodeInfos, *nodeInfo) + } + + bsList := analysisBadSmell(nodeInfos) + + return bsList +} + +func analysisBadSmell(nodes []JClassNode) []BadSmellModel { + var badSmellList []BadSmellModel + for _, node := range nodes { + for _, method := range node.Methods { + if method.StartLine - method.StopLine > 50 { + longMethod := &BadSmellModel{node.Path, string(method.StartLine), "longMethod"} + badSmellList = append(badSmellList, *longMethod) + } + } + } + + return badSmellList; + return nil +} + +func (j *BadSmellApp) javaFiles(codeDir string) []string { + files := make([]string, 0) + _ = filepath.Walk(codeDir, func(path string, fi os.FileInfo, err error) error { + if strings.HasSuffix(path, ".java") && !strings.Contains(path, "Test.java") { + files = append(files, path) + } + return nil + }) + return files +} + +func (j *BadSmellApp) processFile(path string) *JavaParser { + is, _ := antlr.NewFileStream(path) + lexer := NewJavaLexer(is) + stream := antlr.NewCommonTokenStream(lexer, 0); + parser := NewJavaParser(stream) + return parser +} diff --git a/bs/BadSmellListener.go b/bs/BadSmellListener.go new file mode 100644 index 0000000..db003d8 --- /dev/null +++ b/bs/BadSmellListener.go @@ -0,0 +1,200 @@ +package call + +import ( + "github.com/antlr/antlr4/runtime/Go/antlr" + . "github.com/phodal/coca/adapter/models" + . "github.com/phodal/coca/language/java" + "reflect" + "strings" +) + +var imports []string +var clzs []string +var currentPkg string +var currentClz string +var methods []JMethod +var methodCalls []JMethodCall +var currentType string + +var fields = make(map[string]string) +var localVars = make(map[string]string) +var formalParameters = make(map[string]string) + +func NewBadSmellListener() *BadSmellListener { + currentClz = "" + currentPkg = "" + methods = nil + methodCalls = nil + return &BadSmellListener{} +} + +type BadSmellListener struct { + BaseJavaParserListener +} + +func (s *BadSmellListener) getNodeInfo() *JClassNode { + return &JClassNode{currentPkg, currentClz, currentType, "", methods, methodCalls} +} + +func (s *BadSmellListener) EnterPackageDeclaration(ctx *PackageDeclarationContext) { + currentPkg = ctx.QualifiedName().GetText() +} + +func (s *BadSmellListener) EnterImportDeclaration(ctx *ImportDeclarationContext) { + importText := ctx.QualifiedName().GetText() + imports = append(imports, importText) +} + +func (s *BadSmellListener) EnterClassDeclaration(ctx *ClassDeclarationContext) { + currentType = "Class" + currentClz = ctx.IDENTIFIER().GetText() +} + +func (s *BadSmellListener) EnterInterfaceDeclaration(ctx *InterfaceDeclarationContext) { + currentType = "Interface" + currentClz = ctx.IDENTIFIER().GetText() +} + +func (s *BadSmellListener) EnterInterfaceMethodDeclaration(ctx *InterfaceMethodDeclarationContext) { + startLine := ctx.GetStart().GetLine() + startLinePosition := ctx.IDENTIFIER().GetSymbol().GetColumn() + stopLine := ctx.GetStop().GetLine() + name := ctx.IDENTIFIER().GetText() + stopLinePosition := startLinePosition + len(name) + + typeType := ctx.TypeTypeOrVoid().GetText() + + method := &JMethod{name, typeType, startLine, startLinePosition, stopLine, stopLinePosition} + + methods = append(methods, *method) +} + +func (s *BadSmellListener) EnterFormalParameter(ctx *FormalParameterContext) { + formalParameters[ctx.VariableDeclaratorId().GetText()] = ctx.TypeType().GetText() +} + +func (s *BadSmellListener) EnterFieldDeclaration(ctx *FieldDeclarationContext) { + declarators := ctx.VariableDeclarators() + variableName := declarators.GetParent().GetChild(0).(antlr.ParseTree).GetText() + fields[variableName] = ctx.TypeType().GetText() +} + +func (s *BadSmellListener) EnterLocalVariableDeclaration(ctx *LocalVariableDeclarationContext) { + typ := ctx.GetChild(0).(antlr.ParseTree).GetText() + variableName := ctx.GetChild(1).GetChild(0).GetChild(0).(antlr.ParseTree).GetText() + localVars[variableName] = typ +} + +func (s *BadSmellListener) EnterMethodDeclaration(ctx *MethodDeclarationContext) { + startLine := ctx.GetStart().GetLine() + startLinePosition := ctx.IDENTIFIER().GetSymbol().GetColumn() + stopLine := ctx.GetStop().GetLine() + name := ctx.IDENTIFIER().GetText() + stopLinePosition := startLinePosition + len(name) + //XXX: find the start position of {, not public + + typeType := ctx.TypeTypeOrVoid().GetText() + + method := &JMethod{name, typeType, startLine, startLinePosition, stopLine, stopLinePosition} + methods = append(methods, *method) +} + +func (s *BadSmellListener) EnterMethodCall(ctx *MethodCallContext) { + var targetCtx = ctx.GetParent().GetChild(0).(antlr.ParseTree).GetText() + var targetType = parseTargetType(targetCtx) + callee := ctx.GetChild(0).(antlr.ParseTree).GetText() + + startLine := ctx.GetStart().GetLine() + startLinePosition := ctx.GetStart().GetColumn() + stopLine := ctx.GetStop().GetLine() + stopLinePosition := startLinePosition + len(callee) + + //typeType := ctx.GetChild(0).(antlr.ParseTree).TypeTypeOrVoid().GetText() + + fullType := warpTargetFullType(targetType) + if fullType != "" { + jMethodCall := &JMethodCall{removeTarget(fullType), "", targetType, callee, startLine, startLinePosition, stopLine, stopLinePosition} + methodCalls = append(methodCalls, *jMethodCall) + } else { + if ctx.GetText() == targetType { + methodName := ctx.IDENTIFIER().GetText() + jMethodCall := &JMethodCall{currentPkg, "",currentClz, methodName, startLine, startLinePosition, stopLine, stopLinePosition} + methodCalls = append(methodCalls, *jMethodCall) + } + } +} + +func (s *BadSmellListener) EnterExpression(ctx *ExpressionContext) { + // lambda BlogPO::of + if ctx.COLONCOLON() != nil { + text := ctx.Expression(0).GetText() + methodName := ctx.IDENTIFIER().GetText() + targetType := parseTargetType(text) + fullType := warpTargetFullType(targetType) + + + startLine := ctx.GetStart().GetLine() + startLinePosition := ctx.GetStart().GetColumn() + stopLine := ctx.GetStop().GetLine() + stopLinePosition := startLinePosition + len(text) + + jMethodCall := &JMethodCall{removeTarget(fullType), "", targetType, methodName, startLine, startLinePosition, stopLine, stopLinePosition} + methodCalls = append(methodCalls, *jMethodCall) + } +} + +func (s *BadSmellListener) appendClasses(classes []string) { + clzs = classes +} + +func removeTarget(fullType string) string { + split := strings.Split(fullType, ".") + return strings.Join(split[:len(split)-1], ".") +} + +func parseTargetType(targetCtx string) string { + targetVar := targetCtx + targetType := targetVar + + //TODO: update this reflect + typeOf := reflect.TypeOf(targetCtx).String() + if strings.HasSuffix(typeOf, "MethodCallContext") { + targetType = currentClz; + } else { + fieldType := fields[targetVar] + formalType := formalParameters[targetVar] + localVarType := localVars[targetVar] + if fieldType != "" { + targetType = fieldType + } else if formalType != "" { + targetType = formalType; + } else if localVarType != "" { + targetType = localVarType; + } + } + + return targetType +} + +func warpTargetFullType(targetType string) string { + if strings.EqualFold(currentClz, targetType) { + return currentPkg + "." + targetType + } + + for index := range imports { + imp := imports[index] + if strings.HasSuffix(imp, targetType) { + return imp + } + } + + //maybe the same package + for _, clz := range clzs { + if strings.HasSuffix(clz, "." + targetType) { + return clz + } + } + + //1. current package, 2. import by * + return "" +} diff --git a/bs/bs.go b/bs/bs.go deleted file mode 100644 index 73930f3..0000000 --- a/bs/bs.go +++ /dev/null @@ -1,32 +0,0 @@ -package bs -import ( - "encoding/json" - "fmt" - . "github.com/phodal/coca/adapter/models" - . "github.com/phodal/coca/refactor/base/models" - . "github.com/phodal/coca/utils" -) - -var nodes []JMoveStruct - -type BadSmellApp struct { -} - -var depsFile string -var parsedDeps []JClassNode - -func NewBadSmellApp(depPath string) *BadSmellApp { - depsFile = depPath - return &BadSmellApp{} -} - -func (j *BadSmellApp) Start() { - file := ReadFile(depsFile) - if file == nil { - return - } - - _ = json.Unmarshal(file, &parsedDeps) - - fmt.Println(parsedDeps) -} diff --git a/cmd/badsmell.go b/cmd/badsmell.go index a167fbf..c79d918 100644 --- a/cmd/badsmell.go +++ b/cmd/badsmell.go @@ -1,20 +1,27 @@ package cmd import ( - . "github.com/phodal/coca/bs" + "encoding/json" "github.com/spf13/cobra" + + . "github.com/phodal/coca/bs" + . "github.com/phodal/coca/utils" ) var badsmellCmd *cobra.Command = &cobra.Command{ Use: "badsmell", - Short: "badsmell recognized", + Short: "Bad Code Smell", Long: ``, Run: func(cmd *cobra.Command, args []string) { - depFile := cmd.Flag("dependence").Value.String() + importPath := cmd.Flag("path").Value.String() + + if importPath != "" { + bsApp := new(BadSmellApp) + bsList := bsApp.AnalysisPath(importPath) + + bsModel, _ := json.MarshalIndent(bsList, "", "\t") - if depFile != "" { - bsApp := NewBadSmellApp(depFile) - bsApp.Start() + WriteToFile("deps.json", string(bsModel)) } }, } @@ -22,5 +29,5 @@ var badsmellCmd *cobra.Command = &cobra.Command{ func init() { rootCmd.AddCommand(badsmellCmd) - badsmellCmd.PersistentFlags().StringP("dependence", "d", "", "dependence path") + badsmellCmd.PersistentFlags().StringP("path", "p", "Code Path", "example -p src/main") } -- GitLab