11package com.github.surpsg.diffcoverage.extensions
22
3+ import com.form.diff.ClassFile
34import com.github.surpsg.diffcoverage.services.diff.ModifiedFilesService
45import com.intellij.coverage.CoverageSuitesBundle
56import com.intellij.coverage.JavaCoverageAnnotator
@@ -12,9 +13,7 @@ import com.intellij.ide.util.treeView.AbstractTreeNode
1213import com.intellij.openapi.application.ReadAction
1314import com.intellij.openapi.components.service
1415import com.intellij.openapi.project.Project
15- import com.intellij.psi.PsiClass
16- import com.intellij.psi.PsiElement
17- import com.intellij.psi.PsiPackage
16+ import com.intellij.psi.*
1817import com.intellij.psi.search.GlobalSearchScope
1918import java.io.File
2019import java.nio.file.Paths
@@ -28,100 +27,117 @@ class DiffCoverageViewExtension(
2827 annotator, project, suitesBundle, stateBean
2928) {
3029
31- override fun getChildrenNodes ( node : AbstractTreeNode < * > ? ): List < AbstractTreeNode < * >> {
32- val children : MutableList < AbstractTreeNode < * >> = ArrayList ( )
30+ private val codeUpdateInfo = myProject.service< ModifiedFilesService >().obtainCodeUpdateInfo()
31+ private val packageCoverageData = PackageCoverageData (myProject )
3332
33+ override fun getChildrenNodes (node : AbstractTreeNode <* >? ): List <AbstractTreeNode <* >> {
3434 if (node !is CoverageListNode || node.getValue() is PsiClass ) {
35- return children
35+ return emptyList()
3636 }
37+
38+ var children: Sequence <AbstractTreeNode <* >> = emptySequence()
3739 val nodeValue = node.getValue()
3840 if (nodeValue is PsiPackage ) {
39- val packageCoverageData = PackageCoverageData (myProject)
40- if (isInCoverageScope(packageCoverageData, nodeValue)) {
41- directChildrenSequence(nodeValue, PsiPackage ::getSubPackages).forEach { subPackage ->
42- processSubPackage(packageCoverageData, subPackage, children)
43- }
44- directChildrenSequence(nodeValue, PsiPackage ::getFiles).forEach { file ->
45- collectFileChildren(file, node, children)
46- }
47- } else if (! myStateBean.myFlattenPackages) {
48- collectSubPackages(packageCoverageData, children, nodeValue)
49- }
41+ children + = collectClasses(nodeValue)
5042 }
5143 if (node is CoverageListRootNode ) {
52- mySuitesBundle.suites.asSequence()
44+ children + = mySuitesBundle.suites.asSequence()
5345 .flatMap { mySuitesBundle.suites.asSequence() }
5446 .map { suite -> suite as JavaCoverageSuite }
5547 .flatMap { it.getCurrentSuiteClasses(myProject) }
56- .forEach {
57- children + = CoverageListNode (myProject, it, mySuitesBundle, myStateBean)
58- }
48+ .map { CoverageListNode (myProject, it, mySuitesBundle, myStateBean) }
5949 }
6050
61- return children.onEach {
62- it.parent = node
63- }
51+ return children.onEach { it.parent = node }.toList()
6452 }
6553
66- private fun <T > directChildrenSequence (
67- nodePackage : PsiPackage ,
68- childrenItemsGetter : (PsiPackage , GlobalSearchScope ) -> Array <T >
69- ): Sequence <T > {
70- return ReadAction .compute<Sequence <T >, RuntimeException > {
71- if (nodePackage.isValid) {
72- childrenItemsGetter(nodePackage, getSearchScope()).asSequence()
73- } else {
74- emptySequence()
54+ private fun collectClasses (psiPackage : PsiPackage ): Sequence <AbstractTreeNode <* >> {
55+ var children = emptySequence<AbstractTreeNode <* >>()
56+ val currentPackages = ArrayDeque <PsiPackage >().apply { this + = psiPackage }
57+ while (! currentPackages.isEmpty()) {
58+ val currentPackage = currentPackages.removeFirst()
59+ if (getInReadThread { ! currentPackage.isValid }) {
60+ continue
7561 }
76- }
77- }
7862
79- private fun getSearchScope (): GlobalSearchScope {
80- return mySuitesBundle.getSearchScope(myProject)
81- }
63+ if (packageCoverageData.isPackageInScope(currentPackage)) {
64+ children + = currentPackage.directChildrenSequence(PsiPackage ::getFiles).flatMap { file ->
65+ collectChildrenClasses(file)
66+ }
67+ }
8268
83- private fun collectSubPackages (
84- packageCoverageData : PackageCoverageData ,
85- children : MutableList <AbstractTreeNode <* >>,
86- rootPackage : PsiPackage
87- ) {
88- ReadAction .compute<Array <PsiPackage >, RuntimeException > {
89- rootPackage.getSubPackages(getSearchScope())
90- }.forEach {
91- processSubPackage(packageCoverageData, it, children)
69+ currentPackage.directChildrenSequence(PsiPackage ::getSubPackages)
70+ .filter { packageCoverageData.isParentPackageForDiffPackages(it) }
71+ .forEach { currentPackages + = it }
9272 }
73+ return children
9374 }
9475
95- private fun processSubPackage (
96- packageCoverageData : PackageCoverageData ,
97- aPackage : PsiPackage ,
98- children : MutableList <AbstractTreeNode <* >>
99- ) {
100- if (isInCoverageScope(packageCoverageData, aPackage)) {
101- children + = CoverageListNode (myProject, aPackage, mySuitesBundle, myStateBean)
102- } else if (! myStateBean.myFlattenPackages) {
103- collectSubPackages(packageCoverageData, children, aPackage)
104- }
105- if (myStateBean.myFlattenPackages) {
106- collectSubPackages(packageCoverageData, children, aPackage)
76+ private fun <T > PsiPackage.directChildrenSequence (
77+ childrenItemsGetter : (PsiPackage , GlobalSearchScope ) -> Array <T >
78+ ): Sequence <T > {
79+ return getValidValue(emptySequence()) {
80+ val searchScope = mySuitesBundle.getSearchScope(myProject)
81+ childrenItemsGetter(this , searchScope).asSequence()
10782 }
10883 }
10984
110- private fun isInCoverageScope (packageCoverageData : PackageCoverageData , element : PsiElement ): Boolean {
111- return ReadAction .compute<Boolean , RuntimeException > {
112- element is PsiPackage && packageCoverageData.isPackageInScope(element)
85+ private fun collectChildrenClasses (file : PsiFile ): Sequence <AbstractTreeNode <* >> {
86+ return if (file is PsiClassOwner ) {
87+ file.getValidValue(PsiClass .EMPTY_ARRAY , PsiClassOwner ::getClasses)
88+ .asSequence()
89+ .filter { isFileModified(it) }
90+ .map { CoverageListNode (myProject, it, mySuitesBundle, myStateBean) }
91+ } else {
92+ emptySequence()
11393 }
11494 }
11595
96+ private fun isFileModified (psiClass : PsiClass ): Boolean {
97+ val qualifiedClassName = psiClass.getValidValue(null ) { qualifiedName } ? : return false
98+ val classFile = ClassFile (psiClass.containingFile.name, qualifiedClassName)
99+
100+ return codeUpdateInfo.isInfoExists(classFile)
101+ }
102+
116103 class PackageCoverageData (project : Project ) {
117104 private val diffPackages: Set <String > = project.service<ModifiedFilesService >()
118105 .buildPatchCollection().asSequence()
119106 .map { Paths .get(it.path).parent.toString() }
120107 .map { it.replace(File .separator, " ." ) }
121108 .toSet()
122109
123- fun isPackageInScope (psiPackage : PsiPackage ): Boolean = diffPackages.any {
124- it.endsWith(psiPackage.qualifiedName)
110+ fun isPackageInScope (psiPackage : PsiPackage ): Boolean {
111+ return psiPackage.isPackageFiltered { packageCandidate, diffPackage ->
112+ diffPackage.endsWith(packageCandidate)
113+ }
114+ }
115+
116+ fun isParentPackageForDiffPackages (psiPackage : PsiPackage ): Boolean {
117+ return psiPackage.isPackageFiltered { packageCandidate, diffPackage ->
118+ diffPackage.contains(packageCandidate)
119+ }
120+ }
121+
122+ private fun PsiPackage.isPackageFiltered (
123+ filterCondition : (String , String ) -> Boolean
124+ ): Boolean {
125+ val qualifiedNamePackage = ReadAction .compute<String , RuntimeException >(::getQualifiedName)
126+ return diffPackages.any { filterCondition(qualifiedNamePackage, it) }
127+ }
128+ }
129+
130+ private fun <T > getInReadThread (func : () -> T ): T {
131+ return ReadAction .compute<T , RuntimeException >(func)
132+ }
133+
134+ private fun <T : PsiElement , V > T.getValidValue (defaultValue : V , valueGetter : T .() -> V ): V {
135+ return getInReadThread {
136+ if (isValid) {
137+ valueGetter()
138+ } else {
139+ defaultValue
140+ }
125141 }
126142 }
127143}
0 commit comments