Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ ci:
mvn install -DskipTests && \
echo "🧩 Testing with sample project" && \
(cd sample-project && mvn -Dksp.plugin.version=$(PROJECT_VERSION) test) && \
echo "🧩 Testing with arrow-optics-test" && \
(cd test-projects/arrow-optics && mvn -Dksp.plugin.version=$(PROJECT_VERSION) test) && \
echo "βœ… Done"

.PHONY:sample
Expand Down
2 changes: 1 addition & 1 deletion sample-project/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<properties>
<kotlin.version>2.3.10</kotlin.version>
<maven.compiler.release>11</maven.compiler.release>
<ksp.plugin.version>0.3.0</ksp.plugin.version>
<ksp.plugin.version>0.3.2</ksp.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

Expand Down
115 changes: 115 additions & 0 deletions test-projects/arrow-optics/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>arrow-optics-test</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<kotlin.version>2.3.10</kotlin.version>
<arrow.version>2.2.1.1</arrow.version>
<maven.compiler.release>11</maven.compiler.release>
<ksp.plugin.version>0.3.2</ksp.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>

<!--
IMPORTANT: arrow-optics MUST be a project compile dependency (not a plugin dependency).
The KSP processor needs to resolve the @optics annotation type from the project classpath.
Without this, the processor finds no annotated classes and generates nothing.
-->
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-optics</artifactId>
<version>${arrow.version}</version>
</dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<version>${arrow.version}</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>

<plugins>
<plugin>
<groupId>me.kpavlov.ksp.maven</groupId>
<artifactId>ksp-maven-plugin</artifactId>
<version>${ksp.plugin.version}</version>
<!-- REQUIRED: allows plugin to register generated sources in the build lifecycle -->
<extensions>true</extensions>
<configuration>
<debug>true</debug>
</configuration>
<dependencies>
<!--
arrow-optics-ksp-plugin is the KSP symbol processor.
It MUST be a plugin dependency (not a project dependency),
so that Maven's plugin classloader can discover it via ServiceLoader.
-->
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-optics-ksp-plugin</artifactId>
<version>${arrow.version}</version>
</dependency>
</dependencies>
<!-- No explicit <executions> needed: the plugin's lifecycle extension
automatically binds process (main) to generate-sources and
process-test (test sources) to generate-test-sources. -->
</plugin>

<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<phase>compile</phase>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<phase>test-compile</phase>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
</project>
39 changes: 39 additions & 0 deletions test-projects/arrow-optics/src/main/kotlin/com/example/Person.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example

import arrow.optics.optics

/**
* A data class annotated with @optics.
*
* Arrow KSP processor generates Lens/Prism/Optional optics for each property.
* The companion object is REQUIRED for the generated extension properties to attach to.
*
* ```
* Generated (by arrow-optics-ksp-plugin):
* Person.name : Lens<Person, String>
* Person.age : Lens<Person, Int>
* Person.address: Lens<Person, Address>
*/
@optics
data class Person(
val name: String,
val age: Int,
val address: Address,
) {
companion object
}

/**
* A nested data class also annotated with @optics.
*
* Generated (by arrow-optics-ksp-plugin):
* Address.street: Lens<Address, String>
* Address.city : Lens<Address, String>
*/
@optics
data class Address(
val street: String,
val city: String,
) {
companion object
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example

import kotlin.test.Test
import kotlin.test.assertEquals

/**
* Verifies that arrow-optics-ksp-plugin successfully generated optics for Person and Address.
*
* If these tests fail to compile, the KSP processor did not generate the optics.
* Check:
* 1. arrow-optics is in project <dependencies> (compile scope), not only in plugin <dependencies>
* 2. The @optics annotation is applied and companion object is present
* 3. Generated sources dir (target/generated-sources/ksp) is on the compile source path
*/
class PersonOpticsTest {

@Test
fun `Lens modifies name`() {
val person = Person(name = "john", age = 30, address = Address("Main St", "New York"))

// Person.name is a generated Lens<Person, String>
val updated = Person.name.modify(person) { it.replaceFirstChar(Char::uppercaseChar) }

assertEquals("John", updated.name)
assertEquals(30, updated.age)
}

@Test
fun `Lens replaces age`() {
val person = Person(name = "John", age = 30, address = Address("Main St", "New York"))

val updated = Person.age.set(person, 31)

assertEquals(31, updated.age)
assertEquals("John", updated.name)
}

@Test
fun `composed Lens modifies nested city`() {
val person = Person(name = "John", age = 30, address = Address("Main St", "New York"))

// Compose Person.address (Lens<Person,Address>) with Address.city (Lens<Address,String>)
val cityLens = Person.address compose Address.city
val updated = cityLens.set(person, "London")

assertEquals("London", updated.address.city)
assertEquals("Main St", updated.address.street)
}

@Test
fun `Lens get reads property`() {
val person = Person(name = "Alice", age = 25, address = Address("Elm St", "Boston"))

assertEquals("Alice", Person.name.get(person))
assertEquals("Boston", Address.city.get(person.address))
}
}