未验证 提交 2772fb89 编写于 作者: P Phodal Huang

feat: add bs code

上级 f2576cf4
package cmd
import (
"coca/src/bs"
"coca/src/utils"
"encoding/json"
"github.com/spf13/cobra"
"strings"
)
var badsmellCmd *cobra.Command = &cobra.Command{
Use: "bs",
Short: "Bad Code Smell",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
importPath := cmd.Flag("path").Value.String()
ignoreStr := cmd.Flag("ignore").Value.String()
sortType := cmd.Flag("sort").Value.String()
ignoreRules := strings.Split(ignoreStr, ",")
if importPath != "" {
bsApp := new(bs.BadSmellApp)
bsList := bsApp.AnalysisPath(importPath, ignoreRules)
bsModel, _ := json.MarshalIndent(bsList, "", "\t")
if sortType == "type" {
sortSmells := sortSmellByType(bsList)
bsModel, _ = json.MarshalIndent(sortSmells, "", "\t")
}
utils.WriteToFile("bs.json", string(bsModel))
}
},
}
func sortSmellByType(models []bs.BadSmellModel) map[string][]bs.BadSmellModel {
sortSmells := make(map[string][]bs.BadSmellModel)
for _, model := range models {
sortSmells[model.Bs] = append(sortSmells[model.Bs], model)
}
return sortSmells
}
func init() {
rootCmd.AddCommand(badsmellCmd)
badsmellCmd.PersistentFlags().StringP("path", "p", "", "example -p src/main")
badsmellCmd.PersistentFlags().StringP("ignore", "x", "", "-x=dataClass,lazyElement,longMethod,refusedBequest")
badsmellCmd.PersistentFlags().StringP("sort", "s", "", "sort bad smell -s=type")
}
package bs
import (
"coca/src/utils"
"encoding/json"
"fmt"
"github.com/antlr/antlr4/runtime/Go/antlr"
"os"
"path/filepath"
"strconv"
"strings"
. "coca/src/bs/models"
. "coca/src/language/java"
)
var nodeInfos []JFullClassNode
type BadSmellModel struct {
File string `json:"File,omitempty"`
Line string `json:"Line,omitempty"`
Bs string `json:"BS,omitempty"`
Description string `json:"Description,omitempty"`
}
type BadSmellApp struct {
}
func (j *BadSmellApp) AnalysisPath(codeDir string, ignoreRules []string) []BadSmellModel {
nodeInfos = nil
files := (*BadSmellApp)(nil).javaFiles(codeDir)
for index := range files {
nodeInfo := NewJFullClassNode()
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)
}
bsModel, _ := json.MarshalIndent(nodeInfos, "", "\t")
utils.WriteToFile("nodeInfos.json", string(bsModel))
bsList := analysisBadSmell(nodeInfos)
mapIgnoreRules := make(map[string]bool)
for _, ignore := range ignoreRules {
mapIgnoreRules[ignore] = true
}
filteredBsList := filterBadsmellList(bsList, mapIgnoreRules)
return filteredBsList
}
func filterBadsmellList(models []BadSmellModel, ignoreRules map[string]bool) []BadSmellModel {
var results []BadSmellModel
for _, model := range models {
if !ignoreRules[model.Bs] {
results = append(results, model)
}
}
return results
}
func analysisBadSmell(nodes []JFullClassNode) []BadSmellModel {
var badSmellList []BadSmellModel
for _, node := range nodes {
// To be Defined number
if node.Type == "Class" && len(node.Methods) < 1 {
badSmellList = append(badSmellList, *&BadSmellModel{node.Path, "", "lazyElement", ""})
}
onlyHaveGetterAndSetter := true
// Long Method
for _, method := range node.Methods {
methodLength := method.StopLine - method.StartLine
if methodLength > 30 {
description := "method length: " + strconv.Itoa(methodLength)
longMethod := &BadSmellModel{node.Path, strconv.Itoa(method.StartLine), "longMethod", description}
badSmellList = append(badSmellList, *longMethod)
}
if strings.Contains(method.Name, "get") && strings.Contains(method.Name, "set") {
onlyHaveGetterAndSetter = false
}
// longParameterList
if len(method.Parameters) > 6 {
paramsJson, _ := json.Marshal(method.Parameters)
str := string(paramsJson[:])
longParams := &BadSmellModel{node.Path, strconv.Itoa(method.StartLine), "longParameterList", str}
badSmellList = append(badSmellList, *longParams)
}
// repeatedSwitches
if method.MethodBs.IfSize > 8 || method.MethodBs.SwitchSize > 8 {
longParams := &BadSmellModel{node.Path, strconv.Itoa(method.StartLine), "repeatedSwitches", ""}
badSmellList = append(badSmellList, *longParams)
}
}
// dataClass
if onlyHaveGetterAndSetter && node.Type == "Class" && len(node.Methods) > 0 {
dataClass := &BadSmellModel{node.Path, "", "dataClass", ""}
badSmellList = append(badSmellList, *dataClass)
}
//Refused Bequest
if node.Extends != "" {
hasCallParentMethod := false
for _, methodCall := range node.MethodCalls {
if methodCall.Class == node.Extends {
hasCallParentMethod = true
}
}
if !hasCallParentMethod {
badSmellList = append(badSmellList, *&BadSmellModel{node.Path, "", "refusedBequest", ""})
}
}
// LargeClass
normalClassLength := withOutGetterSetterClass(node.Methods)
if node.Type == "Class" && normalClassLength > 20 {
description := "methods number (without getter/setter): " + strconv.Itoa(normalClassLength)
badSmellList = append(badSmellList, *&BadSmellModel{node.Path, "", "largeClass", description})
}
}
return badSmellList
}
func withOutGetterSetterClass(fullMethods []JFullMethod) int {
var normalMethodSize = 0
for _, method := range fullMethods {
if !strings.Contains(method.Name, "get") && !strings.Contains(method.Name, "set") {
normalMethodSize++
}
}
return normalMethodSize
}
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
}
package bs
import (
. "coca/src/bs/models"
. "coca/src/language/java"
"github.com/antlr/antlr4/runtime/Go/antlr"
"reflect"
"strings"
)
var imports []string
var clzs []string
var currentPkg string
var currentClz string
var currentClzType string
var currentClzExtends string
var currentClzImplements []string
var methods []JFullMethod
var methodCalls []JFullMethodCall
var fields = make(map[string]string)
var localVars = make(map[string]string)
var formalParameters = make(map[string]string)
var currentClassBs ClassBadSmellInfo
func NewBadSmellListener() *BadSmellListener {
currentClz = ""
currentPkg = ""
methods = nil
methodCalls = nil
currentClzImplements = nil
currentClzExtends = ""
return &BadSmellListener{}
}
type BadSmellListener struct {
BaseJavaParserListener
}
func (s *BadSmellListener) getNodeInfo() *JFullClassNode {
return &JFullClassNode{
currentPkg,
currentClz,
currentClzType,
"",
currentClzExtends,
currentClzImplements,
methods,
methodCalls,
currentClassBs,
}
}
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) {
currentClzType = "Class"
currentClz = ctx.IDENTIFIER().GetText()
if ctx.EXTENDS() != nil {
currentClzExtends = ctx.TypeType().GetText()
}
if ctx.IMPLEMENTS() != nil {
typeList := ctx.TypeList().(*TypeListContext)
for _, typ := range typeList.AllTypeType() {
typeData := getTypeDATA(typ.(*TypeTypeContext))
currentClzImplements = append(currentClzImplements, typeData)
}
}
}
func getTypeDATA(typ *TypeTypeContext) string {
var typeData string
classOrInterface := typ.ClassOrInterfaceType().(*ClassOrInterfaceTypeContext)
if classOrInterface != nil {
identifiers := classOrInterface.AllIDENTIFIER()
typeData = identifiers[len(identifiers)-1].GetText()
}
return typeData
}
func (s *BadSmellListener) EnterInterfaceDeclaration(ctx *InterfaceDeclarationContext) {
currentClzType = "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)
methodBody := ctx.MethodBody().GetText()
typeType := ctx.TypeTypeOrVoid().GetText()
var methodParams []JFullParameter = nil
parameters := ctx.FormalParameters()
if parameters != nil {
if reflect.TypeOf(parameters.GetChild(1)).String() == "*parser.FormalParameterListContext" {
allFormal := parameters.GetChild(1).(*FormalParameterListContext)
formalParameter := allFormal.AllFormalParameter()
for _, param := range formalParameter {
paramContext := param.(*FormalParameterContext)
paramType := paramContext.TypeType().GetText()
paramValue := paramContext.VariableDeclaratorId().(*VariableDeclaratorIdContext).IDENTIFIER().GetText()
methodParams = append(methodParams, *&JFullParameter{paramType, paramValue})
}
}
}
methodBSInfo := *&MethodBadSmellInfo{0, 0}
method := &JFullMethod{
name,
typeType,
startLine,
startLinePosition,
stopLine,
stopLinePosition,
methodBody,
methodParams,
methodBSInfo,
}
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()
for _, declarator := range declarators.(*VariableDeclaratorsContext).AllVariableDeclarator() {
value := declarator.(*VariableDeclaratorContext).VariableDeclaratorId().(*VariableDeclaratorIdContext).IDENTIFIER().GetText()
fields[value] = variableName
}
}
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()
methodBody := ctx.MethodBody().GetText()
var methodParams []JFullParameter = nil
parameters := ctx.FormalParameters()
if parameters != nil {
if reflect.TypeOf(parameters.GetChild(1)).String() == "*parser.FormalParameterListContext" {
allFormal := parameters.GetChild(1).(*FormalParameterListContext)
formalParameter := allFormal.AllFormalParameter()
for _, param := range formalParameter {
paramContext := param.(*FormalParameterContext)
paramType := paramContext.TypeType().GetText()
paramValue := paramContext.VariableDeclaratorId().(*VariableDeclaratorIdContext).IDENTIFIER().GetText()
methodParams = append(methodParams, *&JFullParameter{paramType, paramValue})
localVars[paramValue] = paramType
}
}
}
methodBSInfo := *&MethodBadSmellInfo{0, 0}
methodBadSmellInfo := buildMethodBSInfo(ctx, methodBSInfo)
method := &JFullMethod{
name,
typeType,
startLine,
startLinePosition,
stopLine,
stopLinePosition,
methodBody,
methodParams,
methodBadSmellInfo,
}
methods = append(methods, *method)
}
func buildMethodBSInfo(context *MethodDeclarationContext, bsInfo MethodBadSmellInfo) MethodBadSmellInfo {
methodBody := context.MethodBody()
blockContext := methodBody.GetChild(0)
if reflect.TypeOf(blockContext).String() == "*parser.BlockContext" {
blcStatement := blockContext.(*BlockContext).AllBlockStatement()
for _, statement := range blcStatement {
if reflect.TypeOf(statement.GetChild(0)).String() == "*parser.StatementContext" {
if len(statement.GetChild(0).(*StatementContext).GetChildren()) < 3 {
continue
}
statementCtx := statement.GetChild(0).(*StatementContext)
if (reflect.TypeOf(statementCtx.GetChild(1)).String()) == "*parser.ParExpressionContext" {
if statementCtx.GetChild(0).(antlr.ParseTree).GetText() == "if" {
bsInfo.IfSize = bsInfo.IfSize + 1
}
if statementCtx.GetChild(0).(antlr.ParseTree).GetText() == "switch" {
bsInfo.SwitchSize = bsInfo.SwitchSize + 1
}
}
}
}
}
return bsInfo
}
func (s *BadSmellListener) EnterFormalParameterList(ctx *FormalParameterListContext) {
//fmt.Println(ctx.GetParent().GetParent().(antlr.RuleNode).get)
//fmt.Println(ctx.AllFormalParameter()
}
func (s *BadSmellListener) EnterAnnotation(ctx *AnnotationContext) {
if currentClzType == "Class" && ctx.QualifiedName().GetText() == "Override" {
currentClassBs.OverrideSize++
}
}
func (s *BadSmellListener) EnterCreator(ctx *CreatorContext) {
variableName := ctx.GetParent().GetParent().GetChild(0).(antlr.ParseTree).GetText()
localVars[variableName] = ctx.CreatedName().GetText()
}
func (s *BadSmellListener) EnterLocalTypeDeclaration(ctx *LocalTypeDeclarationContext) {
}
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()
// TODO: 处理链试调用
if strings.Contains(targetType, "()") && strings.Contains(targetType, ".") {
split := strings.Split(targetType, ".")
sourceTarget := split[0]
targetType = localVars[sourceTarget]
}
fullType := warpTargetFullType(targetType)
if targetType == "super" {
targetType = currentClzExtends
}
if fullType != "" {
jMethodCall := &JFullMethodCall{removeTarget(fullType), "", targetType, callee, startLine, startLinePosition, stopLine, stopLinePosition}
methodCalls = append(methodCalls, *jMethodCall)
} else {
if ctx.GetText() == targetType {
methodName := ctx.IDENTIFIER().GetText()
jMethodCall := &JFullMethodCall{currentPkg, "", currentClz, methodName, startLine, startLinePosition, stopLine, stopLinePosition}
methodCalls = append(methodCalls, *jMethodCall)
} else {
methodName := ctx.IDENTIFIER().GetText()
jMethodCall := &JFullMethodCall{currentPkg, "NEEDFIX", targetType, 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 := &JFullMethodCall{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
}
// TODO: update for array
split := strings.Split(targetType, ".")
str := split[0]
pureTargetType := strings.ReplaceAll(strings.ReplaceAll(str, "[", ""), "]", "")
for index := range imports {
imp := imports[index]
if strings.HasSuffix(imp, pureTargetType) {
return imp
}
}
//maybe the same package
for _, clz := range clzs {
if strings.HasSuffix(clz, "."+pureTargetType) {
return clz
}
}
//1. current package, 2. import by *
if pureTargetType == "super" {
for index := range imports {
imp := imports[index]
if strings.HasSuffix(imp, currentClzExtends) {
return imp
}
}
}
return ""
}
package models
type JFullClassNode struct {
Package string
Class string
Type string
Path string
Extends string
Implements []string
Methods []JFullMethod
MethodCalls []JFullMethodCall
ClassBS ClassBadSmellInfo
}
type JFullMethodCall struct {
Package string
Type string
Class string
MethodName string
StartLine int
StartLinePosition int
StopLine int
StopLinePosition int
}
type JFullMethod struct {
Name string
Type string
StartLine int
StartLinePosition int
StopLine int
StopLinePosition int
MethodBody string
Parameters []JFullParameter
MethodBs MethodBadSmellInfo
}
type MethodBadSmellInfo struct {
IfSize int
SwitchSize int
}
type ClassBadSmellInfo struct {
OverrideSize int
PublicVarSize int
}
type JFullParameter struct {
Name string
Type string
}
func NewJFullClassNode() *JFullClassNode {
info := &ClassBadSmellInfo{0, 0};
return &JFullClassNode{
"",
"",
"",
"",
"",
nil,
nil,
nil,
*info}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册