From fe79991b3fb3199e3d09aa8f601100f386b45d1f Mon Sep 17 00:00:00 2001 From: Jia Liu Date: Tue, 9 Jan 2024 15:25:04 +0800 Subject: [PATCH 1/3] docs: list todo rules in javaServiceAnalyser --- .../cc/unitmesh/quality/extension/JavaServiceAnalyser.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt index 6951568e..3bcd9366 100644 --- a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt +++ b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt @@ -9,6 +9,13 @@ class JavaServiceAnalyser(thresholds: Map = mapOf()) : QualityAnaly private val webApiRuleSetProvider = WebApiRuleSetProvider() override fun analysis(nodes: List): List { + //todo rule1: service 长度小于x00行 -- bad smell large class + //todo rule2: 调用的repository数量 -- 数量越少越好 + //todo rule3: 调用的repository之间的关联度 -- ER关系上,关联度越强越好 + //todo rule4: 不依赖controller层相关概念,如:request/response -- 层与层之间依赖关系清晰 + //todo rule5: public filed数量 -- service应该都是private filed + //pending rule6: 不被外界访问的public method -- 应归属到bad smell + // 检查 Service 长度,调用的 repository 数量, return listOf() } From f76cc78673fc2c48e92171ef81c666653c98aa4c Mon Sep 17 00:00:00 2001 From: Jia Liu Date: Tue, 9 Jan 2024 15:50:11 +0800 Subject: [PATCH 2/3] test: should return empty list of issues when the java service analyser analysis non-service node --- .../quality/extension/JavaServiceAnalyser.kt | 2 +- .../quality/JavaServiceAnalyserTest.kt | 23 ++++++ .../resources/java/structs_HelloService.json | 70 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt create mode 100644 code-quality/src/test/resources/java/structs_HelloService.json diff --git a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt index 3bcd9366..a3d21ed3 100644 --- a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt +++ b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt @@ -9,12 +9,12 @@ class JavaServiceAnalyser(thresholds: Map = mapOf()) : QualityAnaly private val webApiRuleSetProvider = WebApiRuleSetProvider() override fun analysis(nodes: List): List { - //todo rule1: service 长度小于x00行 -- bad smell large class //todo rule2: 调用的repository数量 -- 数量越少越好 //todo rule3: 调用的repository之间的关联度 -- ER关系上,关联度越强越好 //todo rule4: 不依赖controller层相关概念,如:request/response -- 层与层之间依赖关系清晰 //todo rule5: public filed数量 -- service应该都是private filed //pending rule6: 不被外界访问的public method -- 应归属到bad smell + //pending rule1: service 长度小于x00行 -- bad smell large class // 检查 Service 长度,调用的 repository 数量, return listOf() diff --git a/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt b/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt new file mode 100644 index 00000000..f4d06173 --- /dev/null +++ b/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt @@ -0,0 +1,23 @@ +package cc.unitmesh.quality; + +import cc.unitmesh.quality.extension.JavaServiceAnalyser +import chapi.domain.core.CodeDataStruct +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Test +import java.io.File +import kotlin.test.assertEquals + +class JavaServiceAnalyserTest { + private fun loadNodes(source: String): List { + return Json { ignoreUnknownKeys = true }.decodeFromString( + File(this.javaClass.classLoader.getResource(source)!!.file).readText() + ) + } + @Test + fun `should return empty list of issues when node is not a service`() { + val nodes = loadNodes("java/structs_HelloController.json") + val issues = JavaServiceAnalyser().analysis(nodes) + + assertEquals(0, issues.size) + } +} diff --git a/code-quality/src/test/resources/java/structs_HelloService.json b/code-quality/src/test/resources/java/structs_HelloService.json new file mode 100644 index 00000000..8bcb26dc --- /dev/null +++ b/code-quality/src/test/resources/java/structs_HelloService.json @@ -0,0 +1,70 @@ +[ + { + "NodeName": "HelloController", + "Type": "CLASS", + "Package": "com.example.springboot", + "FilePath": "HelloController.java", + "Fields": [], + "MultipleExtend": [], + "Implements": [], + "Functions": [ + { + "Name": "index", + "ReturnType": "String", + "MultipleReturns": [], + "Parameters": [], + "FunctionCalls": [], + "Annotations": [ + { + "Name": "GetMapping", + "KeyValues": [ + { + "Key": "\"/blog/get\"", + "Value": "\"/blog/get\"" + } + ] + } + ], + "Modifiers": [], + "InnerStructures": [], + "InnerFunctions": [], + "Position": { + "StartLine": 11, + "StartLinePosition": 11, + "StopLine": 13, + "StopLinePosition": 4 + }, + "LocalVariables": [] + } + ], + "InnerStructures": [], + "Annotations": [ + { + "Name": "RestController", + "KeyValues": [] + }, + { + "Name": "RequestMapping", + "KeyValues": [] + } + ], + "FunctionCalls": [], + "Parameters": [], + "Imports": [ + { + "Source": "org.springframework.web.bind.annotation.GetMapping", + "UsageName": [] + }, + { + "Source": "org.springframework.web.bind.annotation.RestController", + "UsageName": [] + } + ], + "Exports": [], + "Position": { + "StartLine": 8, + "StartLinePosition": 7, + "StopLine": 15 + } + } +] \ No newline at end of file From f50ceff356d527dff645431fb1f765fdad6465e8 Mon Sep 17 00:00:00 2001 From: Jia Liu Date: Tue, 9 Jan 2024 17:50:19 +0800 Subject: [PATCH 3/3] feat: should return too many repository dependencies issue when anaysis java service that depends on more then 5 repositories --- .../quality/extension/JavaServiceAnalyser.kt | 22 ++++-- .../quality/extension/rule/ServiceRule.kt | 9 +++ .../rule/TooManyRepositoryDependenciesRule.kt | 30 ++++++++ .../quality/JavaServiceAnalyserTest.kt | 22 ++++++ .../java/ServiceWithSixRepositories.java | 30 ++++++++ .../resources/java/structs_HelloService.json | 70 ------------------- 6 files changed, 109 insertions(+), 74 deletions(-) create mode 100644 code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/ServiceRule.kt create mode 100644 code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/TooManyRepositoryDependenciesRule.kt create mode 100644 code-quality/src/test/resources/java/ServiceWithSixRepositories.java delete mode 100644 code-quality/src/test/resources/java/structs_HelloService.json diff --git a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt index a3d21ed3..a875013e 100644 --- a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt +++ b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt @@ -1,15 +1,29 @@ package cc.unitmesh.quality.extension import cc.unitmesh.quality.QualityAnalyser +import cc.unitmesh.quality.extension.rule.TooManyRepositoryDependenciesRule import chapi.domain.core.CodeDataStruct -import org.archguard.linter.rule.webapi.WebApiRuleSetProvider import org.archguard.rule.core.Issue +import org.archguard.rule.core.IssuePosition +import org.archguard.rule.core.Rule +import org.archguard.rule.core.RuleType class JavaServiceAnalyser(thresholds: Map = mapOf()) : QualityAnalyser { - private val webApiRuleSetProvider = WebApiRuleSetProvider() - override fun analysis(nodes: List): List { - //todo rule2: 调用的repository数量 -- 数量越少越好 + val serviceNodes = nodes.filter { it.filterAnnotations("Service").isNotEmpty() } + if (serviceNodes.isNotEmpty()) { + val results: MutableList = mutableListOf() + TooManyRepositoryDependenciesRule().visitRoot(serviceNodes, fun(rule: Rule, position: IssuePosition) { + results += Issue( + position, + ruleId = rule.key, + name = rule.name, + detail = rule.description, + ruleType = RuleType.SERVICE_SMELL + ) + }) + return results; + } //todo rule3: 调用的repository之间的关联度 -- ER关系上,关联度越强越好 //todo rule4: 不依赖controller层相关概念,如:request/response -- 层与层之间依赖关系清晰 //todo rule5: public filed数量 -- service应该都是private filed diff --git a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/ServiceRule.kt b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/ServiceRule.kt new file mode 100644 index 00000000..3847b1e5 --- /dev/null +++ b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/ServiceRule.kt @@ -0,0 +1,9 @@ +package cc.unitmesh.quality.extension.rule + +import chapi.domain.core.CodeDataStruct +import org.archguard.rule.core.IssueEmit +import org.archguard.rule.core.Rule + +open class ServiceRule : Rule() { + open fun visitRoot(rootNode: List, callback: IssueEmit) {} +} \ No newline at end of file diff --git a/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/TooManyRepositoryDependenciesRule.kt b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/TooManyRepositoryDependenciesRule.kt new file mode 100644 index 00000000..ee7f7618 --- /dev/null +++ b/code-quality/src/main/kotlin/cc/unitmesh/quality/extension/rule/TooManyRepositoryDependenciesRule.kt @@ -0,0 +1,30 @@ +package cc.unitmesh.quality.extension.rule + +import chapi.domain.core.CodeDataStruct +import org.archguard.rule.core.IssueEmit +import org.archguard.rule.core.IssuePosition +import org.archguard.rule.core.Severity + +/** + * Service should not dependent more than 5 repositories. + */ +const val LIMIT = 5 + +class TooManyRepositoryDependenciesRule : ServiceRule() { + init { + this.id = "too-many-repository-dependencies" + this.name = "TooManyRepositoryDependencies" + this.key = this.javaClass.name + this.severity = Severity.WARN + this.description = "Service should not dependent more than 5 repositories." + } + + override fun visitRoot(rootNodes: List, callback: IssueEmit) { + rootNodes.forEach { + val repositoryCount = it.Fields.filter { it.TypeType.contains("Repository", true) }.count() + if (repositoryCount > LIMIT) { + callback(this, IssuePosition()) + } + } + } +} \ No newline at end of file diff --git a/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt b/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt index f4d06173..405f88fa 100644 --- a/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt +++ b/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt @@ -1,10 +1,14 @@ package cc.unitmesh.quality; import cc.unitmesh.quality.extension.JavaServiceAnalyser +import chapi.ast.javaast.JavaAnalyser import chapi.domain.core.CodeDataStruct import kotlinx.serialization.json.Json +import org.archguard.rule.core.RuleType +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.io.File +import java.nio.file.Paths import kotlin.test.assertEquals class JavaServiceAnalyserTest { @@ -13,6 +17,7 @@ class JavaServiceAnalyserTest { File(this.javaClass.classLoader.getResource(source)!!.file).readText() ) } + @Test fun `should return empty list of issues when node is not a service`() { val nodes = loadNodes("java/structs_HelloController.json") @@ -20,4 +25,21 @@ class JavaServiceAnalyserTest { assertEquals(0, issues.size) } + + @Test + fun `should identify too many repository dependencies`() { + val path = getAbsolutePath("java/ServiceWithSixRepositories.java") + val data = JavaAnalyser().analysis(File(path).readText(), "ServiceWithSixRepositories.java").DataStructures + val issues = JavaServiceAnalyser().analysis(data) + + Assertions.assertEquals(1, issues.size) + Assertions.assertEquals("TooManyRepositoryDependencies", issues[0].name) + Assertions.assertEquals("Service should not dependent more than 5 repositories.", issues[0].detail) + Assertions.assertEquals(RuleType.SERVICE_SMELL, issues[0].ruleType) + } + + private fun getAbsolutePath(path: String): String { + val resource = this.javaClass.classLoader.getResource(path) + return Paths.get(resource!!.toURI()).toFile().absolutePath + } } diff --git a/code-quality/src/test/resources/java/ServiceWithSixRepositories.java b/code-quality/src/test/resources/java/ServiceWithSixRepositories.java new file mode 100644 index 00000000..6ae90e4a --- /dev/null +++ b/code-quality/src/test/resources/java/ServiceWithSixRepositories.java @@ -0,0 +1,30 @@ +package com.afs.restapi.service; + +import com.afs.restapi.repository.CompanyRepository; +import com.afs.restapi.repository.EmployeeRepository; +import com.afs.restapi.repository.DepartmentRepository; +import com.afs.restapi.repository.TeamRepository; +import com.afs.restapi.repository.GroupRepository; +import com.afs.restapi.repository.CommunitRepository; +import org.springframework.stereotype.Service; + +@Service +public class Example { + private CompanyRepository companyRepository; + private EmployeeRepository employeeRepository; + private DepartmentRepository departmentRepository; + private TeamRepository teamRepository; + private GroupRepository groupRepository; + private CommunitRepository communitRepository; + + public Example(CompanyRepository companyRepository, EmployeeRepository employeeRepository, + DepartmentRepository departmentRepository, TeamRepository teamRepository, + GroupRepository groupRepository, CommunitRepository communitRepository) { + this.companyRepository = companyRepository; + this.employeeRepository = employeeRepository; + this.departmentRepository = departmentRepository; + this.teamRepository = teamRepository; + this.groupRepository = groupRepository; + this.communitRepository = communitRepository; + } +} diff --git a/code-quality/src/test/resources/java/structs_HelloService.json b/code-quality/src/test/resources/java/structs_HelloService.json deleted file mode 100644 index 8bcb26dc..00000000 --- a/code-quality/src/test/resources/java/structs_HelloService.json +++ /dev/null @@ -1,70 +0,0 @@ -[ - { - "NodeName": "HelloController", - "Type": "CLASS", - "Package": "com.example.springboot", - "FilePath": "HelloController.java", - "Fields": [], - "MultipleExtend": [], - "Implements": [], - "Functions": [ - { - "Name": "index", - "ReturnType": "String", - "MultipleReturns": [], - "Parameters": [], - "FunctionCalls": [], - "Annotations": [ - { - "Name": "GetMapping", - "KeyValues": [ - { - "Key": "\"/blog/get\"", - "Value": "\"/blog/get\"" - } - ] - } - ], - "Modifiers": [], - "InnerStructures": [], - "InnerFunctions": [], - "Position": { - "StartLine": 11, - "StartLinePosition": 11, - "StopLine": 13, - "StopLinePosition": 4 - }, - "LocalVariables": [] - } - ], - "InnerStructures": [], - "Annotations": [ - { - "Name": "RestController", - "KeyValues": [] - }, - { - "Name": "RequestMapping", - "KeyValues": [] - } - ], - "FunctionCalls": [], - "Parameters": [], - "Imports": [ - { - "Source": "org.springframework.web.bind.annotation.GetMapping", - "UsageName": [] - }, - { - "Source": "org.springframework.web.bind.annotation.RestController", - "UsageName": [] - } - ], - "Exports": [], - "Position": { - "StartLine": 8, - "StartLinePosition": 7, - "StopLine": 15 - } - } -] \ No newline at end of file