diff --git a/codebase-graph-builder/pom.xml b/codebase-graph-builder/pom.xml index 606cdad..5adea5f 100644 --- a/codebase-graph-builder/pom.xml +++ b/codebase-graph-builder/pom.xml @@ -18,7 +18,7 @@ org.openrewrite.recipe rewrite-recipe-bom - 3.4.0 + 3.33.0 pom import diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java index 790f07b..1c09895 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java @@ -5,7 +5,9 @@ import lombok.extern.slf4j.Slf4j; import org.openrewrite.ExecutionContext; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavadocVisitor; import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.Javadoc; @Slf4j public class MetricsCollectingVisitor extends JavaIsoVisitor { @@ -22,6 +24,21 @@ public MetricsCollectingVisitor(MetricsCollector metricsCollector) { this.metricsCollector = metricsCollector; } + /** + * Returns a JavadocVisitor that does nothing. This is done to prevent the visitor from including references in + * Javadocs as metric counts + * @return JavadocVisitor that does nothing. + */ + @Override + protected JavadocVisitor getJavadocVisitor() { + return new JavadocVisitor<>(this) { + @Override + public Javadoc visitDocComment(Javadoc.DocComment docComment, ExecutionContext ctx) { + return docComment; + } + }; + } + @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { currentSourcePath = cu.getSourcePath().toString(); // .toUri().toString(); diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java index 40a3edf..b30ac9e 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java @@ -5,7 +5,9 @@ import lombok.extern.slf4j.Slf4j; import org.hjug.graphbuilder.DependencyCollector; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavadocVisitor; import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.Javadoc; /** * BUG: Static method calls and definitions are not being captured, but were previously being captured. @@ -38,6 +40,21 @@ protected DependencyCollector getDependencyCollector() { }; } + /** + * Returns a JavadocVisitor that does nothing. This is done to prevent the visitor from including references in + * Javadocs as members of cycles + * @return JavadocVisitor that does nothing. + */ + @Override + protected JavadocVisitor

getJavadocVisitor() { + return new JavadocVisitor<>(this) { + @Override + public Javadoc visitDocComment(Javadoc.DocComment docComment, P p) { + return docComment; + } + }; + } + @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { JavaType.FullyQualified type = classDecl.getType(); diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java index b4085c4..9916380 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java @@ -851,4 +851,37 @@ void sourceFilePathCapturedForAllClasses() throws IOException { "sourceFilePath should not be null for " + classMetrics.getFullyQualifiedName()); } } + + @Test + void javadocMethodReferenceIsNotCountedAsForeignMethodCall() throws IOException { + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc"); + + JavaParser javaParser = JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + DefaultDirectedWeightedGraph classGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + DefaultDirectedWeightedGraph packageGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + GraphMetricsCollector metricsCollector = new GraphMetricsCollector(classGraph, packageGraph); + MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); + + List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser + .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) + .forEach(cu -> metricsVisitor.visit(cu, ctx)); + + metricsCollector.finalizeMetrics(); + + ClassMetrics metrics = metricsCollector.getClassMetrics( + "org.hjug.graphbuilder.metrics.testclasses.javadoc.MethodWithJavadocReference"); + Assertions.assertNotNull(metrics, "MethodWithJavadocReference should be collected"); + + Assertions.assertEquals( + 0, + metrics.getCouplingBetweenObjects(), + "CBO must be 0: Semaphore.acquire() is Javadoc-only and must not be counted as a foreign dependency. Got: " + + metrics.getCouplingBetweenObjects()); + } } diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc/JavaDocServiceClass.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc/JavaDocServiceClass.java new file mode 100644 index 0000000..886116f --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc/JavaDocServiceClass.java @@ -0,0 +1,7 @@ +package org.hjug.graphbuilder.metrics.testclasses.javadoc; + +public class JavaDocServiceClass { + public String serviceName = "service"; + + public void serviceMethod() {} +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc/MethodWithJavadocReference.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc/MethodWithJavadocReference.java new file mode 100644 index 0000000..17f9f5a --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/javadoc/MethodWithJavadocReference.java @@ -0,0 +1,15 @@ +package org.hjug.graphbuilder.metrics.testclasses.javadoc; + +/** + * Class whose methods reference external fields only in Javadoc. + */ +public class MethodWithJavadocReference { + + /** + * Does nothing in its body. + * See {@link JavaDocServiceClass#serviceName} for context. + */ + public void doSomething() { + // intentionally empty - JavaDocServiceClass.serviceName is not accessed here + } +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java index 160a413..e05df53 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java @@ -33,6 +33,7 @@ class JavaVisitorTest { private static final String INITIALIZERS = TESTCLASSES + "/initializers"; private static final String VARIABLE_INITIALIZERS = TESTCLASSES + "/variableInitializers"; private static final String TRY_CATCH = TESTCLASSES + "/tryCatch"; + private static final String JAVADOC_TESTCLASSES = TESTCLASSES + "/javadoc"; private static String repoFrom(String pathString) { return new File(pathString).toURI().toString().replace("/" + pathString, ""); @@ -84,7 +85,7 @@ void visitClasses_registersExpectedPackageCount() throws IOException { javaParser .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) .forEach(cu -> javaVisitor.visit(cu, ctx)); - assertEquals(7, dependencyCollector.getPackagesInCodebase().size()); + assertEquals(8, dependencyCollector.getPackagesInCodebase().size()); } @Test @@ -361,6 +362,16 @@ void catchClauseTypeIsCountedOnce() throws IOException { "catch clause exception type is double-processed by visitTry manual loop after super already handled it"); } + @Test + void javadocReferencedClassDoesNotCreateSpuriousDependencyEdge() throws IOException { + Graph graph = buildAndVisit(JAVADOC_TESTCLASSES); + assertFalse( + graph.containsEdge( + "org.hjug.graphbuilder.visitor.testclasses.javadoc.JavaDocOwner", + "org.hjug.graphbuilder.visitor.testclasses.javadoc.JavaDocSibling"), + "JavaDocSibling is referenced only in Javadoc {@link} and must not create a dependency edge from JavaDocOwner"); + } + private static double getEdgeWeight( Graph classReferencesGraph, String sourceVertex, String targetVertex) { return classReferencesGraph.getEdgeWeight(classReferencesGraph.getEdge(sourceVertex, targetVertex)); diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/javadoc/JavaDocOwner.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/javadoc/JavaDocOwner.java new file mode 100644 index 0000000..5c51c33 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/javadoc/JavaDocOwner.java @@ -0,0 +1,21 @@ +package org.hjug.graphbuilder.visitor.testclasses.javadoc; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class that references {@link JavaDocSibling} only in documentation. + */ +public class JavaDocOwner { + private List items = new ArrayList<>(); + + /** + * Does something unrelated to JavaDocSibling. + * See also {@link JavaDocSibling#publicMethod()} for a related operation. + * + * @param name the name + */ + public void doSomething(String name) { + items.add(name); + } +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/javadoc/JavaDocSibling.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/javadoc/JavaDocSibling.java new file mode 100644 index 0000000..62c5244 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/javadoc/JavaDocSibling.java @@ -0,0 +1,5 @@ +package org.hjug.graphbuilder.visitor.testclasses.javadoc; + +public class JavaDocSibling { + public void publicMethod() {} +}