-
Notifications
You must be signed in to change notification settings - Fork 0
[Draft] CombinedField の自動生成 #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
f8f2f34
chore
TBSten 9427c99
feat: add @GenerateCombinedField annotation and test cases
TBSten 2a36864
feat: implement KSP plugin for @GenerateCombinedField with recursive …
TBSten 9cf2e33
test: add comprehensive tests for @GenerateCombinedField generated code
TBSten edfabdc
feat: add Nullable, Enum, and Value class support to KSP Plugin
TBSten 519bc2e
Merge branch 'main' into feature/auto-generate-combined-field
TBSten 0659d19
chore: regenerate API files after merge
TBSten bc18e15
fix: replace dots with underscores in generated property names
TBSten 8ca4c6b
docs: add requirements and supported types to @GenerateCombinedField …
TBSten e99fc88
fix: improve error message to include actual property count
TBSten 2225ff6
fix: correct TransformField syntax for value class fields
TBSten 367808e
fix: handle nullable types in recursive dependency detection
TBSten 1e1523e
feat: add error message for unsupported generic types
TBSten d8ac2e7
refactor: extract common primitive type mapping logic
TBSten b7255ab
Merge branch 'main' into feature/auto-generate-combined-field
TBSten 5bbb7ee
Merge main into feature/auto-generate-combined-field
TBSten a8cd7e6
feat: support sealed interface/class for @GenerateCombinedField
TBSten 9adc6c1
refactor: split GenerateCombinedField.kt into separate files
TBSten acd9085
Merge branch 'main' into feature/auto-generate-combined-field
TBSten 216fc39
fix: correct generated code for value classes and MutablePreviewLabFi…
TBSten 8d02f04
fix: correct PreviewLab import path in TestScreens.kt
TBSten e75f00f
fix: correct MutablePreviewLabField import path in test files
TBSten File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
...nMain/kotlin/me/tbsten/compose/preview/lab/generatecombinedfield/GenerateCombinedField.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package me.tbsten.compose.preview.lab.generatecombinedfield | ||
|
|
||
| /** | ||
| * Annotation to generate a field extension function for a data class or sealed interface/class. | ||
| * | ||
| * ## For Data Classes | ||
| * | ||
| * When applied to a data class with a companion object, this will generate | ||
| * a `field` extension function on the companion object that creates a | ||
| * MutablePreviewLabField<T> with CombinedField. | ||
| * | ||
| * ### Requirements for Data Classes | ||
| * | ||
| * - Must be a `data class` | ||
| * - Must have a `companion object` | ||
| * - Must have a primary constructor | ||
| * - Must have at least 1 property | ||
| * - Must have at most 10 properties | ||
| * | ||
| * ### Example for Data Classes | ||
| * | ||
| * ``` | ||
| * @GenerateCombinedField | ||
| * data class MyUiState(val str: String, val int: Int, val bool: Boolean) { | ||
| * companion object | ||
| * } | ||
| * | ||
| * // Generates: | ||
| * fun MyUiState.Companion.field(label: String, initialValue: MyUiState): MutablePreviewLabField<MyUiState> = ... | ||
| * ``` | ||
| * | ||
| * ## For Sealed Interfaces/Classes | ||
| * | ||
| * When applied to a sealed interface or sealed class with a companion object, | ||
| * this will generate a `field` extension function that creates a | ||
| * MutablePreviewLabField<T> with PolymorphicField, automatically detecting all | ||
| * subclasses and generating appropriate fields for each. | ||
| * | ||
| * ### Requirements for Sealed Types | ||
| * | ||
| * - Must be a `sealed interface` or `sealed class` | ||
| * - Must have a `companion object` | ||
| * - All direct subclasses must be: | ||
| * - `data class` (will generate CombinedField) | ||
| * - `data object` or `object` (will generate FixedField) | ||
| * | ||
| * ### Example for Sealed Interfaces | ||
| * | ||
| * ``` | ||
| * @GenerateCombinedField | ||
| * sealed interface UiState { | ||
| * data object Loading : UiState | ||
| * data class Success(val data: String) : UiState | ||
| * data class Error(val message: String) : UiState | ||
| * companion object | ||
| * } | ||
| * | ||
| * // Generates: | ||
| * fun UiState.Companion.field(label: String, initialValue: UiState): MutablePreviewLabField<UiState> = | ||
| * PolymorphicField( | ||
| * label = label, | ||
| * initialValue = initialValue, | ||
| * fields = listOf( | ||
| * FixedField("Loading", UiState.Loading), | ||
| * combined(label = "Success", ...) { UiState.Success(it) }, | ||
| * combined(label = "Error", ...) { UiState.Error(it) }, | ||
| * ), | ||
| * ) | ||
| * ``` | ||
| * | ||
| * ## Supported Property Types | ||
| * | ||
| * - Primitive types: String, Int, Float, Boolean, etc. | ||
| * - Compose value types: Dp, Color, etc. | ||
| * - Nullable types | ||
| * - Enum types | ||
| * - Value classes | ||
| * - Nested data classes with companion objects (recursive generation) | ||
| */ | ||
| @Target(AnnotationTarget.CLASS) | ||
| @Retention(AnnotationRetention.SOURCE) | ||
| annotation class GenerateCombinedField | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
...src/commonMain/kotlin/me/tbsten/compose/preview/lab/sample/lib/testcase/ComplexUiState.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| package me.tbsten.compose.preview.lab.sample.lib.testcase | ||
|
|
||
| import me.tbsten.compose.preview.lab.generatecombinedfield.GenerateCombinedField | ||
|
|
||
| /** | ||
| * Test case 3: Complex combination with multiple nested levels and various types | ||
| * | ||
| * This tests: | ||
| * - Multiple levels of nesting | ||
| * - Various primitive types (String, Int, Boolean, Float) | ||
| * - Multiple nested data classes in a single parent | ||
| */ | ||
|
|
||
| @GenerateCombinedField | ||
| data class UserInfo( | ||
| val name: String, | ||
| val age: Int, | ||
| val isVerified: Boolean, | ||
| ) { | ||
| companion object | ||
| } | ||
|
|
||
| fun UserInfo.Companion.fake() = UserInfo( | ||
| name = "John Doe", | ||
| age = 30, | ||
| isVerified = true, | ||
| ) | ||
|
|
||
| @GenerateCombinedField | ||
| data class AppSettings( | ||
| val isDarkMode: Boolean, | ||
| val fontSize: Int, | ||
| val volume: Float, | ||
| ) { | ||
| companion object | ||
| } | ||
|
|
||
| fun AppSettings.Companion.fake() = AppSettings( | ||
| isDarkMode = false, | ||
| fontSize = 14, | ||
| volume = 0.5f, | ||
| ) | ||
|
|
||
| @GenerateCombinedField | ||
| data class ComplexUiState( | ||
| val userInfo: UserInfo, | ||
| val settings: AppSettings, | ||
| val isLoading: Boolean, | ||
| val errorMessage: String, | ||
| ) { | ||
| companion object | ||
| } | ||
|
|
||
| fun ComplexUiState.Companion.fake() = ComplexUiState( | ||
| userInfo = UserInfo.fake(), | ||
| settings = AppSettings.fake(), | ||
| isLoading = false, | ||
| errorMessage = "", | ||
| ) | ||
|
|
||
| /** | ||
| * Expected generated code: | ||
| * | ||
| * fun UserInfo.Companion.field(label: String, initialValue: UserInfo) = CombinedField3( | ||
| * label = label, | ||
| * field1 = StringField("name", initialValue = initialValue.name), | ||
| * field2 = IntField("age", initialValue = initialValue.age), | ||
| * field3 = BooleanField("isVerified", initialValue = initialValue.isVerified), | ||
| * combine = { name, age, isVerified -> UserInfo(name = name, age = age, isVerified = isVerified) }, | ||
| * split = { splitedOf(it.name, it.age, it.isVerified) }, | ||
| * ) | ||
| * | ||
| * fun AppSettings.Companion.field(label: String, initialValue: AppSettings) = CombinedField3( | ||
| * label = label, | ||
| * field1 = BooleanField("isDarkMode", initialValue = initialValue.isDarkMode), | ||
| * field2 = IntField("fontSize", initialValue = initialValue.fontSize), | ||
| * field3 = FloatField("volume", initialValue = initialValue.volume), | ||
| * combine = { isDarkMode, fontSize, volume -> AppSettings(isDarkMode = isDarkMode, fontSize = fontSize, volume = volume) }, | ||
| * split = { splitedOf(it.isDarkMode, it.fontSize, it.volume) }, | ||
| * ) | ||
| * | ||
| * fun ComplexUiState.Companion.field(label: String, initialValue: ComplexUiState) = CombinedField4( | ||
| * label = label, | ||
| * field1 = UserInfo.field(label = "userInfo", initialValue = initialValue.userInfo), | ||
| * field2 = AppSettings.field(label = "settings", initialValue = initialValue.settings), | ||
| * field3 = BooleanField("isLoading", initialValue = initialValue.isLoading), | ||
| * field4 = StringField("errorMessage", initialValue = initialValue.errorMessage), | ||
| * combine = { userInfo, settings, isLoading, errorMessage -> | ||
| * ComplexUiState( | ||
| * userInfo = userInfo, | ||
| * settings = settings, | ||
| * isLoading = isLoading, | ||
| * errorMessage = errorMessage | ||
| * ) | ||
| * }, | ||
| * split = { splitedOf(it.userInfo, it.settings, it.isLoading, it.errorMessage) }, | ||
| * ) | ||
| */ |
67 changes: 67 additions & 0 deletions
67
.../src/commonMain/kotlin/me/tbsten/compose/preview/lab/sample/lib/testcase/NestedUiState.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package me.tbsten.compose.preview.lab.sample.lib.testcase | ||
|
|
||
| import me.tbsten.compose.preview.lab.generatecombinedfield.GenerateCombinedField | ||
|
|
||
| /** | ||
| * Test case 2: Nested data classes | ||
| * | ||
| * This tests the case where a data class contains another data class | ||
| * that also has @GenerateCombinedField annotation. | ||
| * | ||
| * Expected behavior: | ||
| * - Section1State.Companion.field() should be generated | ||
| * - NestedUiState.Companion.field() should be generated | ||
| * - NestedUiState's field should use Section1State.field() internally | ||
| */ | ||
| @GenerateCombinedField | ||
| data class Section1State( | ||
| val heading: String, | ||
| val body: String, | ||
| ) { | ||
| companion object | ||
| } | ||
|
|
||
| fun Section1State.Companion.fake() = Section1State( | ||
| heading = "Section Heading", | ||
| body = "Section body text goes here", | ||
| ) | ||
|
|
||
| @GenerateCombinedField | ||
| data class NestedUiState( | ||
| val section1State: Section1State, | ||
| val enableButton: Boolean, | ||
| ) { | ||
| companion object | ||
| } | ||
|
|
||
| fun NestedUiState.Companion.fake() = NestedUiState( | ||
| section1State = Section1State.fake(), | ||
| enableButton = true, | ||
| ) | ||
|
|
||
| /** | ||
| * Expected generated code: | ||
| * | ||
| * fun Section1State.Companion.field(label: String, initialValue: Section1State) = CombinedField2( | ||
| * label = label, | ||
| * field1 = StringField("heading", initialValue = initialValue.heading), | ||
| * field2 = StringField("body", initialValue = initialValue.body), | ||
| * combine = { heading, body -> | ||
| * Section1State(heading = heading, body = body) | ||
| * }, | ||
| * split = { splitedOf(it.heading, it.body) }, | ||
| * ) | ||
| * | ||
| * fun NestedUiState.Companion.field(label: String, initialValue: NestedUiState): MutablePreviewLabField<NestedUiState> = CombinedField2( | ||
| * label = label, | ||
| * field1 = Section1State.field(label = "section1State", initialValue = initialValue.section1State), | ||
| * field2 = BooleanField(label = "enableButton", initialValue = initialValue.enableButton), | ||
| * combine = { section1State, enableButton -> | ||
| * NestedUiState( | ||
| * section1State = section1State, | ||
| * enableButton = enableButton | ||
| * ) | ||
| * }, | ||
| * split = { splitedOf(it.section1State, it.enableButton) }, | ||
| * ) | ||
| */ |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.