refactor(jvm-libs): extract config documentation tooling into config-docs module#3606
refactor(jvm-libs): extract config documentation tooling into config-docs module#3606gauravahuja wants to merge 2 commits into
Conversation
…docs module Per review on #3567: the config-doc annotations and engine lived in coordinator/app/src/main, shipping build/analysis tooling in the production jar and blocking reuse by Maru. Introduce jvm-libs/linea/config-docs (package linea.config.docs) holding the reusable engine: ConfigDoc/ConfigSection annotations, ConfigKey, ConfigSchemaWalker, ConfigDocValidator, and the JSON/Markdown generators, with unit tests. Section detection is now parameterized via a SectionDetector (sectionByPackagePrefix helper) instead of a hardcoded coordinator package, so other apps (Maru) can supply their own. Remove the moved annotations (Phase 0) and walker (Phase 1) from coordinator:app and drop its now-unused kotlin-reflect dependency. App wiring (compileOnly dep + configDocs source set + tasks) follows in the next PR. Refs: #3567 Signed-off-by: Gaurav Ahuja <gauravahuja9@gmail.com>
There was a problem hiding this comment.
Pull request overview
Extracts the Hoplite TOML config documentation/validation/generation tooling out of coordinator:app into a reusable JVM library module (jvm-libs:linea:config-docs, package linea.config.docs) so other apps can share the same engine without bundling it into the coordinator production artifact set.
Changes:
- Adds new
jvm-libs:linea:config-docsmodule containing@ConfigDoc/@ConfigSection,ConfigSchemaWalker,ConfigDocValidator, and JSON/Markdown generators, plus unit tests. - Refactors section discovery to be pluggable via a
SectionDetector(withsectionByPackagePrefix(...)) instead of hardcoding coordinator package logic. - Removes the previous coordinator-local tests for these utilities and drops the explicit
kotlin-reflectdependency fromcoordinator:app.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle | Registers the new jvm-libs:linea:config-docs Gradle module. |
| jvm-libs/linea/config-docs/build.gradle | Defines the new library module dependencies (kotlin-reflect, hoplite-core, jackson-databind). |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDoc.kt | Moves and generalizes @ConfigDoc / @ConfigSection annotations into the shared module. |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigKey.kt | Moves the ConfigKey model into the shared module and updates docs for section deprecation metadata. |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigFileRoot.kt | Introduces per-config-file root metadata to support multi-file schema/doc generation. |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt | Moves walker and adds SectionDetector/sectionByPackagePrefix(...) to make section detection app-pluggable. |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDocValidator.kt | Adds shared validator + deterministic human-readable violation report formatting. |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDocJsonGenerator.kt | Adds JSON snapshot generator organized per file, with stable key ordering. |
| jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDocMarkdownGenerator.kt | Adds Markdown reference generator with per-file/per-section tables plus a deprecated-keys appendix. |
| jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigDocTest.kt | Tests runtime readability of @ConfigDoc/@ConfigSection in the new module. |
| jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigSchemaWalkerTest.kt | Tests walker typing/requiredness, map handling, recursion, and undocumented key surfacing using the new SectionDetector. |
| jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigDocValidatorTest.kt | Tests validator rules and deterministic report formatting. |
| jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigDocGeneratorsTest.kt | Tests JSON/Markdown generator shape and determinism. |
| coordinator/app/build.gradle | Removes the explicit kotlin-reflect dependency from the coordinator app. |
| coordinator/app/src/test/kotlin/linea/coordinator/config/v2/toml/ConfigSchemaWalkerAnnotationTest.kt | Removes coordinator-local annotation/walker tests (now covered in config-docs). |
| coordinator/app/src/test/kotlin/linea/coordinator/config/v2/toml/ConfigDocTest.kt | Removes coordinator-local annotation tests (now covered in config-docs). |
| coordinator/app/src/test/kotlin/linea/coordinator/config/v2/docs/ConfigSchemaWalkerTest.kt | Removes coordinator-local walker tests (now covered in config-docs). |
| coordinator/app/src/test/kotlin/linea/coordinator/config/v2/docs/ConfigKeyRenderingTest.kt | Removes coordinator-local key-rendering test coverage (now covered in config-docs). |
Comments suppressed due to low confidence (2)
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt:106
- Cycle protection does not treat self-referential section types as a cycle: when
sectionClass == kClassandonStackis empty, this will recurse once into the same class and emit nested keys (e.g.node.child.child...) instead of stopping immediately. This is easy to hit if a config data class contains an optional reference to itself.
coordinator/app/build.gradle:69 coordinator:appstill directly uses Kotlin reflection extensions (e.g.kotlin.reflect.full.memberProperties/primaryConstructorincoordinator/app/src/main/kotlin/linea/coordinator/config/v2/ConfigLogging.kt), so dropping the explicitkotlin-reflectdependency makes the module rely on a transitive dependency (likely from Hoplite). Other modules in this repo that use Kotlin reflection declare it directly (e.g.maru/core/build.gradle:26,linea-besu/plugins/state-recovery/besu-plugin/build.gradle:13). Consider re-adding an explicitkotlin-reflectdependency here to avoid accidental breakage if transitive deps change.
implementation "info.picocli:picocli:${libs.versions.picoli.get()}"
implementation "io.vertx:vertx-web-client"
implementation "com.sksamuel.hoplite:hoplite-core:${libs.versions.hoplite.get()}"
implementation "com.sksamuel.hoplite:hoplite-toml:${libs.versions.hoplite.get()}"
❌ 1 Tests Failed:
View the full list of 1 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt:106
- Self-referential section types (a config data class containing a parameter of its own type) will recurse once into itself and duplicate keys because the recursion guard doesn't consider the current class. Including the current class in the guard prevents that edge case and keeps cycle detection consistent.
Add ConfigDocsRunner (walk+validate+report exit code; generate+writeIfChanged idempotently) plus generic ConfigDocsCheckMain/ConfigDocsGenerateMain entry points and a ConfigDocsSpec interface. Apps declare a single ConfigDocsSpec object (roots, section detector, output paths, title) and name it from build.gradle; the generic mains load it by class name, so apps need no check/generate entry-point code and the Gradle plugin stays zero-logic. Refs: #3567 Signed-off-by: Gaurav Ahuja <gauravahuja9@gmail.com>
8134ef6 to
ef74d31
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt:27
sectionByPackagePrefix(...)currently usesqualifiedName.startsWith(prefix), which can accidentally match unrelated packages (e.g. prefixlinea.config.docswould also matchlinea.config.docs2.*). Tightening this to require a.boundary after the prefix avoids false-positive section classification.
Summary
Reworks the config-documentation tooling per review on #3567 (fluentcrafter): the annotations and engine lived in
coordinator/app/src/main, so they shipped in the production jar and could not be reused by Maru.This PR extracts the reusable engine into a new
jvm-libs/linea/config-docsmodule (packagelinea.config.docs):ConfigDoc/ConfigSectionannotationsConfigKey,ConfigSchemaWalker,ConfigDocValidatorConfigDocJsonGenerator,ConfigDocMarkdownGenerator,ConfigFileRootKey genericity change: section detection is now a
SectionDetectorparameter (with asectionByPackagePrefix(...)helper) instead of a hardcoded coordinator package, so other apps (Maru) can supply their own. The generators and walker take the detector.The moved annotations (Phase 0, #3463) and walker (Phase 1, #3488) are removed from
coordinator:app, along with its now-unused explicitkotlin-reflectdependency. App wiring follows in the next PR.Rework plan
This is PR1 of a 4-PR rework that supersedes #3567 / #3568 / #3588:
config-docsmodule.compileOnlydep +configDocssource set + check/generate tasks.Nothing is wired into
coordinator:appyet, so behaviour is unchanged there (Phase 0/1 code simply relocated).Test plan
./gradlew :jvm-libs:linea:config-docs:test— passes../gradlew :jvm-libs:linea:config-docs:spotlessCheck :coordinator:app:spotlessCheck— passes../gradlew :coordinator:app:compileKotlin :coordinator:app:compileTestKotlin— passes (no dangling references to the moved classes).Note
Low Risk
Refactor-only: tooling is relocated and generalized with unit tests; coordinator is not yet depending on the new module, so production paths are unaffected.
Overview
Moves Hoplite TOML config documentation out of
coordinator:appinto a new sharedjvm-libs:linea:config-docslibrary (linea.config.docs), so the engine is not bundled in the coordinator production jar and can be reused (e.g. Maru).The library holds
@ConfigDoc/@ConfigSection, schema walking, completeness validation, JSON/Markdown generators,ConfigDocsRunner, and Gradle entry points driven by a per-appConfigDocsSpec. Section detection is no longer tied to the coordinator package: callers pass aSectionDetector(withsectionByPackagePrefixfor the usual case).coordinator:appdrops the moved sources/tests and the explicitkotlin-reflectdependency;settings.gradleregisters the new module. Coordinator Gradle wiring and generated docs are intentionally not in this PR—runtime behavior there is unchanged until follow-ups land.Reviewed by Cursor Bugbot for commit ef74d31. Bugbot is set up for automated code reviews on this repo. Configure here.