Skip to content

Commit e73eb06

Browse files
committed
Merge branch 'main' into trace
* main: parse the declarations of named companion objects and their members fixes #705 use graphviz-java for dot graph generation
2 parents d01672c + 38f42df commit e73eb06

File tree

18 files changed

+3258
-416
lines changed

18 files changed

+3258
-416
lines changed

gradle/libs.versions.toml

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ google-dagger = "2.42"
3333
google-material = "1.4.0"
3434

3535
gradleDoctor = "0.8.1"
36+
graphviz = "0.18.1"
3637
groovy = "3.0.10"
3738
jUnit = "5.8.2"
3839
javaParser = "3.24.2"
@@ -112,6 +113,10 @@ google-dagger-compiler = { module = "com.google.dagger:dagger-compiler", version
112113

113114
google-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "google-ksp" }
114115

116+
graphviz-java = { module = "guru.nidi:graphviz-java", version.ref = "graphviz" }
117+
graphviz-java-min = { module = "guru.nidi:graphviz-java-min-deps", version.ref = "graphviz" }
118+
graphviz-kotlin = { module = "guru.nidi:graphviz-kotlin", version.ref = "graphviz" }
119+
115120
groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
116121
groovyXml = { module = "org.codehaus.groovy:groovy-xml", version.ref = "groovy" }
117122

modulecheck-core/src/test/kotlin/modulecheck/core/DepthOutputTest.kt

+22-19
Original file line numberDiff line numberDiff line change
@@ -281,25 +281,28 @@ internal class DepthOutputTest : RunnerTest() {
281281

282282
if (graphsReport) {
283283
testProjectDir.child("graphs", "app", "main.dot") shouldHaveText """
284-
strict digraph DependencyGraph {
285-
ratio = 0.5625;
286-
node [style = "rounded,filled" shape = box];
287-
288-
labelloc = "t"
289-
label = ":app -- main";
290-
291-
":app" [fillcolor = "#F89820"];
292-
":lib1" [fillcolor = "#F89820"];
293-
":lib2" [fillcolor = "#F89820"];
294-
295-
":app" -> ":lib1" [style = bold; color = "#007744"];
296-
":app" -> ":lib2" [style = bold; color = "#007744"];
297-
298-
":lib2" -> ":lib1" [style = bold; color = "#007744"];
299-
300-
{ rank = same; ":lib1"; }
301-
{ rank = same; ":lib2"; }
302-
{ rank = same; ":app"; }
284+
strict digraph {
285+
edge ["dir"="forward"]
286+
graph ["ratio"="0.5625","rankdir"="TB","label"=<<b>:app -- main</b>>,"labelloc"="t"]
287+
node ["style"="rounded,filled","shape"="box"]
288+
{
289+
edge ["dir"="none"]
290+
graph ["rank"="same"]
291+
":lib1" ["fillcolor"="#F89820"]
292+
}
293+
{
294+
edge ["dir"="none"]
295+
graph ["rank"="same"]
296+
":lib2" ["fillcolor"="#F89820"]
297+
}
298+
{
299+
edge ["dir"="none"]
300+
graph ["rank"="same"]
301+
":app" ["fillcolor"="#F89820"]
302+
}
303+
":app" -> ":lib2" ["arrowhead"="normal","style"="bold","color"="#FF6347"]
304+
":app" -> ":lib1" ["arrowhead"="normal","style"="bold","color"="#FF6347"]
305+
":lib2" -> ":lib1" ["arrowhead"="normal","style"="bold","color"="#FF6347"]
303306
}
304307
"""
305308
} else {

modulecheck-core/src/test/kotlin/modulecheck/core/UnusedDependenciesTest.kt

+130
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,136 @@ class UnusedDependenciesTest : RunnerTest() {
834834
.clean() shouldBe """ModuleCheck found 0 issues"""
835835
}
836836

837+
@Test
838+
fun `module contributing a named companion object, consumed in the same package should not be unused`() {
839+
// https://github.com/RBusarow/ModuleCheck/issues/705
840+
841+
settings.deleteUnused = false
842+
843+
val lib1 = kotlinProject(":lib1") {
844+
addKotlinSource(
845+
"""
846+
package com.modulecheck.common
847+
848+
class Lib1Class {
849+
companion object Factory {
850+
fun create() = Lib1Class()
851+
}
852+
}
853+
""".trimIndent()
854+
)
855+
}
856+
857+
val lib2 = kotlinProject(":lib2") {
858+
addDependency(ConfigurationName.implementation, lib1)
859+
860+
buildFile {
861+
"""
862+
plugins {
863+
kotlin("jvm")
864+
}
865+
866+
dependencies {
867+
implementation(project(path = ":lib1"))
868+
}
869+
"""
870+
}
871+
872+
addKotlinSource(
873+
"""
874+
package com.modulecheck.common
875+
876+
fun foo() {
877+
bar(Lib1Class.create())
878+
}
879+
880+
fun bar(any: Any) = Unit
881+
""".trimIndent(),
882+
SourceSetName.MAIN
883+
)
884+
}
885+
886+
run().isSuccess shouldBe true
887+
888+
lib2.buildFile shouldHaveText """
889+
plugins {
890+
kotlin("jvm")
891+
}
892+
893+
dependencies {
894+
implementation(project(path = ":lib1"))
895+
}
896+
"""
897+
898+
logger.parsedReport() shouldBe listOf()
899+
}
900+
901+
@Test
902+
fun `module contributing a named companion object, consumed by the companion name should not be unused`() {
903+
// https://github.com/RBusarow/ModuleCheck/issues/705
904+
905+
settings.deleteUnused = false
906+
907+
val lib1 = kotlinProject(":lib1") {
908+
addKotlinSource(
909+
"""
910+
package com.modulecheck.common
911+
912+
class Lib1Class {
913+
companion object Factory {
914+
fun create() = Lib1Class()
915+
}
916+
}
917+
""".trimIndent()
918+
)
919+
}
920+
921+
val lib2 = kotlinProject(":lib2") {
922+
addDependency(ConfigurationName.implementation, lib1)
923+
924+
buildFile {
925+
"""
926+
plugins {
927+
kotlin("jvm")
928+
}
929+
930+
dependencies {
931+
implementation(project(path = ":lib1"))
932+
}
933+
"""
934+
}
935+
936+
addKotlinSource(
937+
"""
938+
package com.modulecheck.common
939+
940+
import com.modulecheck.common.Lib1Class.Factory
941+
942+
fun foo() {
943+
bar(Factory.create())
944+
}
945+
946+
fun bar(any: Any) = Unit
947+
""".trimIndent(),
948+
SourceSetName.MAIN
949+
)
950+
}
951+
952+
run().isSuccess shouldBe true
953+
954+
lib2.buildFile shouldHaveText """
955+
plugins {
956+
kotlin("jvm")
957+
}
958+
959+
dependencies {
960+
implementation(project(path = ":lib1"))
961+
}
962+
"""
963+
964+
logger.parsedReport() shouldBe listOf()
965+
}
966+
837967
@Test
838968
fun `testImplementation used in test should not be unused`() {
839969

modulecheck-gradle/plugin/dependencies/runtimeClasspath.txt

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ com.rickbusarow.dispatch:dispatch-core:1.0.0-beta10
1313
com.squareup.anvil:annotations:2.4.0
1414
com.squareup.moshi:moshi:1.13.0
1515
com.squareup.okio:okio:2.10.0
16+
guru.nidi:graphviz-java-min-deps:0.18.1
17+
guru.nidi:graphviz-java:0.18.1
1618
javax.inject:javax.inject:1
1719
net.java.dev.jna:jna:5.6.0
1820
net.swiftzer.semver:semver:1.2.0
@@ -40,4 +42,7 @@ org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.2
4042
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2
4143
org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.2
4244
org.jetbrains:annotations:13.0
45+
org.slf4j:jcl-over-slf4j:1.7.30
46+
org.slf4j:jul-to-slf4j:1.7.30
47+
org.slf4j:slf4j-api:1.7.30
4348
org.unbescape:unbescape:1.1.6.RELEASE

modulecheck-gradle/plugin/src/test/kotlin/modulecheck/gradle/GraphReportTaskTest.kt

+22-19
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,28 @@ internal class GraphReportTaskTest : BaseGradleTest() {
6565
app.projectDir.child(
6666
"build", "reports", "modulecheck", "graphs", "main.dot"
6767
) shouldHaveText """
68-
strict digraph DependencyGraph {
69-
ratio = 0.5625;
70-
node [style = "rounded,filled" shape = box];
71-
72-
labelloc = "t"
73-
label = ":app -- main";
74-
75-
":app" [fillcolor = "#F89820"];
76-
":lib1" [fillcolor = "#F89820"];
77-
":lib2" [fillcolor = "#F89820"];
78-
79-
":app" -> ":lib1" [style = bold; color = "#007744"];
80-
":app" -> ":lib2" [style = bold; color = "#007744"];
81-
82-
":lib2" -> ":lib1" [style = bold; color = "#007744"];
83-
84-
{ rank = same; ":lib1"; }
85-
{ rank = same; ":lib2"; }
86-
{ rank = same; ":app"; }
68+
strict digraph {
69+
edge ["dir"="forward"]
70+
graph ["ratio"="0.5625","rankdir"="TB","label"=<<b>:app -- main</b>>,"labelloc"="t"]
71+
node ["style"="rounded,filled","shape"="box"]
72+
{
73+
edge ["dir"="none"]
74+
graph ["rank"="same"]
75+
":lib1" ["fillcolor"="#F89820"]
76+
}
77+
{
78+
edge ["dir"="none"]
79+
graph ["rank"="same"]
80+
":lib2" ["fillcolor"="#F89820"]
81+
}
82+
{
83+
edge ["dir"="none"]
84+
graph ["rank"="same"]
85+
":app" ["fillcolor"="#F89820"]
86+
}
87+
":app" -> ":lib2" ["arrowhead"="normal","style"="bold","color"="#FF6347"]
88+
":app" -> ":lib1" ["arrowhead"="normal","style"="bold","color"="#FF6347"]
89+
":lib2" -> ":lib1" ["arrowhead"="normal","style"="bold","color"="#FF6347"]
8790
}
8891
"""
8992
}

modulecheck-parsing/psi/src/main/kotlin/modulecheck/parsing/psi/RealKotlinFile.kt

+23-11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import modulecheck.parsing.psi.internal.callSiteName
2222
import modulecheck.parsing.psi.internal.getByNameOrIndex
2323
import modulecheck.parsing.psi.internal.getChildrenOfTypeRecursive
2424
import modulecheck.parsing.psi.internal.identifier
25+
import modulecheck.parsing.psi.internal.isCompanionObject
26+
import modulecheck.parsing.psi.internal.isInCompanionObject
2527
import modulecheck.parsing.psi.internal.isJvmStatic
2628
import modulecheck.parsing.psi.internal.isPartOf
2729
import modulecheck.parsing.psi.internal.isPrivateOrInternal
@@ -146,18 +148,29 @@ class RealKotlinFile(
146148
}
147149
}
148150

151+
val psi = this@declaredNames
152+
153+
fun parseCompanionObjectDeclarations(companionName: String) {
154+
both(nameAsString)
155+
156+
if (isStatic()) {
157+
both(nameAsString.remove(".$companionName"))
158+
} else if (psi is KtCallableDeclaration) {
159+
kotlin(nameAsString.remove(".$companionName"))
160+
}
161+
}
162+
149163
when {
150-
nameAsString.contains(".Companion") -> {
151-
both(nameAsString)
164+
psi.isCompanionObject() -> {
165+
parseCompanionObjectDeclarations(psi.name ?: "Companion")
166+
}
152167

153-
if (isStatic()) {
154-
both(nameAsString.remove(".Companion"))
155-
} else if (this@declaredNames is KtCallableDeclaration) {
156-
kotlin(nameAsString.remove(".Companion"))
157-
}
168+
psi.isInCompanionObject() -> {
169+
val companion = containingClassOrObject as KtObjectDeclaration
170+
parseCompanionObjectDeclarations(companion.name ?: "Companion")
158171
}
159172

160-
isTopLevelKtOrJavaMember() && this@declaredNames !is KtClassOrObject && !isStatic() -> {
173+
isTopLevelKtOrJavaMember() && psi !is KtClassOrObject && !isStatic() -> {
161174
kotlin(nameAsString)
162175

163176
jvmSimpleNames().forEach {
@@ -190,7 +203,7 @@ class RealKotlinFile(
190203

191204
val jvmNames = jvmSimpleNames()
192205

193-
if (this@declaredNames is KtFunction && jvmNameOrNull() == null) {
206+
if (psi is KtFunction && psi.jvmNameOrNull() == null) {
194207
both(nameAsString)
195208
} else {
196209
kotlin(nameAsString)
@@ -204,8 +217,7 @@ class RealKotlinFile(
204217
}
205218
}
206219

207-
this@declaredNames is KtParameter ||
208-
(this@declaredNames is KtProperty && !isTopLevelKtOrJavaMember()) -> {
220+
psi is KtParameter || (psi is KtProperty && !psi.isTopLevelKtOrJavaMember()) -> {
209221

210222
kotlin(nameAsString)
211223

0 commit comments

Comments
 (0)