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..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,14 +1,35 @@ 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 { + 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 + //pending rule6: 不被外界访问的public method -- 应归属到bad smell + //pending rule1: service 长度小于x00行 -- bad smell large class + // 检查 Service 长度,调用的 repository 数量, return listOf() } 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 new file mode 100644 index 00000000..405f88fa --- /dev/null +++ b/code-quality/src/test/kotlin/cc/unitmesh/quality/JavaServiceAnalyserTest.kt @@ -0,0 +1,45 @@ +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 { + 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) + } + + @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; + } +}