diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/KotlinDependencyVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/KotlinDependencyVisitor.java index 0cb4cad0..9b863890 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/KotlinDependencyVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/KotlinDependencyVisitor.java @@ -163,21 +163,6 @@ public K.ClassDeclaration visitClassDeclaration(K.ClassDeclaration classDeclarat DependencyVisitorLogic.recordClassLocation(state, owningFqn, sourcePath); } - // Delegate J-level class declaration processing to shared logic - // Note: Kotlin doesn't have record components in the same way - var snapshot = DependencyVisitorLogic.enterClassDeclaration( - state, - jcd, - false, // processRecordComponents = false for Kotlin - cursor -> { - J.CompilationUnit jcu = cursor.firstEnclosing(J.CompilationUnit.class); - if (jcu != null) { - return jcu.getSourcePath().toUri().toString(); - } - K.CompilationUnit kcu = cursor.firstEnclosing(K.CompilationUnit.class); - return kcu != null ? kcu.getSourcePath().toUri().toString() : null; - }); - // Process Kotlin-specific: type constraints if (classDeclaration.getTypeConstraints() != null) { for (J.TypeParameter typeParameter : @@ -186,7 +171,6 @@ public K.ClassDeclaration visitClassDeclaration(K.ClassDeclaration classDeclarat } } - DependencyVisitorLogic.leaveClassDeclaration(state, snapshot); return result; } diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/KotlinGraphBuilderTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/KotlinGraphBuilderTest.java index 8fcf7fd3..9de69c49 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/KotlinGraphBuilderTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/KotlinGraphBuilderTest.java @@ -65,6 +65,21 @@ void parseKotlinSourceDirectoryTest() throws IOException { "com.ideacrest.parser.testclasses.D")); } + @DisplayName("Kotlin class-header dependencies are recorded exactly once.") + @Test + void classHeaderDependencyHasWeightOne() throws IOException { + File srcDirectory = new File("src/test/resources/kotlinClassHeaderSrcDirectory"); + CodebaseGraphDTO dto = compositeGraphBuilder.getCodebaseGraphDTO(srcDirectory.getAbsolutePath(), false, ""); + Graph classReferencesGraph = dto.getClassReferencesGraph(); + String source = "com.ideacrest.parser.classheader.HeaderImplementation"; + String target = "com.ideacrest.parser.classheader.HeaderContract"; + + assertTrue(classReferencesGraph.containsVertex(source)); + assertTrue(classReferencesGraph.containsVertex(target)); + assertTrue(classReferencesGraph.containsEdge(source, target)); + assertEquals(1.0, getEdgeWeight(classReferencesGraph, source, target)); + } + @DisplayName("Kotlin callable references produce edges between caller and target's declaring class.") @Test void parseKotlinCallableReferenceTest() throws IOException { diff --git a/codebase-graph-builder/src/test/resources/kotlinClassHeaderSrcDirectory/com/ideacrest/parser/classheader/ClassHeader.kt b/codebase-graph-builder/src/test/resources/kotlinClassHeaderSrcDirectory/com/ideacrest/parser/classheader/ClassHeader.kt new file mode 100644 index 00000000..e1230ab9 --- /dev/null +++ b/codebase-graph-builder/src/test/resources/kotlinClassHeaderSrcDirectory/com/ideacrest/parser/classheader/ClassHeader.kt @@ -0,0 +1,5 @@ +package com.ideacrest.parser.classheader + +interface HeaderContract + +class HeaderImplementation : HeaderContract diff --git a/plans/kotlin-implementation-plan-glm-5-2.md b/plans/kotlin-implementation-plan-glm-5-2.md index 72ad6f4f..8d3e1853 100644 --- a/plans/kotlin-implementation-plan-glm-5-2.md +++ b/plans/kotlin-implementation-plan-glm-5-2.md @@ -12,7 +12,7 @@ based on the existing Java analysis architecture in the `codebase-graph-builder` - **KotlinParser.builder().build()** - Analogous to `JavaParser.fromJavaVersion().build()` - Returns `Stream` from `parseInputs()` - Supports `.kt` and `.kts` file extensions -- Uses `org.openrewrite.kotlin.KotlinParser.KotlinLanguageLevel` enum (default: `KOTLIN_2_2`) +- Uses `org.openrewrite.kotlin.KotlinParser.KotlinLanguageLevel` enum (configured as `KOTLIN_2_4`) ### Visitor Architecture @@ -28,8 +28,9 @@ based on the existing Java analysis architecture in the `codebase-graph-builder` ### Dependency Management -- `rewrite-bom:8.86.0` (imported via `rewrite-recipe-bom:3.34.0`) manages `org.openrewrite:rewrite-kotlin:8.86.0` -- Kotlin compiler-embeddable is a transitive dependency (~30 MB) — make optional +- `rewrite-bom:8.90.4` manages `org.openrewrite:rewrite-kotlin:8.90.4` +- `rewrite-kotlin` is a required compile dependency of `codebase-graph-builder`; its Kotlin compiler transitives are + therefore present for every consumer - Java 17 compatible (Kotlin stdlib targets JVM 1.8) ## Design Strategy @@ -63,15 +64,15 @@ based on the existing Java analysis architecture in the `codebase-graph-builder` **Production**: - `CompositeGraphBuilder` walks `.java` and `.kt` files -- Reflectively probes `KotlinParser` presence (optional jar support) -- `GraphBuilderConfig.analyzeKotlin` default `true` +- Runs Java and Kotlin analysis unconditionally and merges both results +- Uses `rewrite-kotlin` directly; there is no reflective parser probe or `GraphBuilderConfig.analyzeKotlin` switch ### Phase 3 — Kotlin Metrics Collection **Red Test**: `KotlinMetricsCollectionTest` — LOC/NOM/NOA/WMC/ATFD/TCC on Kotlin fixtures **Production**: -- Refactor `MetricsCollectingVisitor` → `AbstractMetricsCollectingVisitor` (protected hooks) +- Extract shared metric handling into `MetricsVisitorLogic` with traversal state held by `MetricsVisitorState` - Create `KotlinMetricsCollectingVisitor extends KotlinIsoVisitor` - Handle `K.Property` (top-level properties, extension properties) @@ -149,7 +150,8 @@ Four new disharmony types: ## Locked Design Decisions 1. **Refactor** J-level logic into protected hooks on abstract bases (no fork-and-drift) -2. **Optional Maven dependency** — `rewrite-kotlin` marked `true` -3. **Kotlin language level**: `KOTLIN_2_2` (parser default), configurable via `GraphBuilderConfig` +2. **Required Maven dependency** — `rewrite-kotlin` is a non-optional compile dependency of + `codebase-graph-builder`; `CompositeGraphBuilder` runs Kotlin analysis unconditionally +3. **Kotlin language level**: `KOTLIN_2_4`, configurable via `GraphBuilderConfig` 4. **Kotlin disharmonies as ClassDisharmony** — reuses existing downstream plumbing -5. **Callable references & type parameters feed BOTH graph edges AND metrics** \ No newline at end of file +5. **Callable references & type parameters feed BOTH graph edges AND metrics**