From 3c1dfabcb0779cc0df4c6609579d237f1c5f41c7 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 6 Aug 2026 13:12:32 +0200 Subject: [PATCH] C2: export KOTLIN_BANNER and merged_file_path Both were marked `pub` but sit in private modules and were never re-exported, so they were reachable from inside the crate and from nowhere else. An audit of every `pub` item confirms these are the only two. They are worth having: KOTLIN_BANNER is what `KtFile::banner` falls back to, so a consumer prepending its own header has nothing to match against without it; merged_file_path is how write_files lays out its output, and predicting those paths without writing anything is a reasonable thing to want. The new test is written against the public paths, so it stops compiling if either is un-exported again. --- src/file.rs | 6 +++++- src/lib.rs | 3 ++- src/render.rs | 7 ++++++- src/tests.rs | 20 ++++++++++++++++++-- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/file.rs b/src/file.rs index 75ecc92..8d0b6ca 100644 --- a/src/file.rs +++ b/src/file.rs @@ -105,7 +105,11 @@ pub fn merge_files(fragments: Vec) -> Result, WriteKotlinErr Ok(groups.into_values().collect()) } -/// The flattened on-disk path of one merged file under `kotlin_root`. +/// The flattened on-disk path of one merged file under `kotlin_root`: +/// `io.zenoh.jni.bytes` becomes `/io/zenoh/jni/bytes.kt`. +/// +/// [`write_files`] uses this to lay out its output; it is public so a consumer +/// can predict, report or post-process those paths without writing anything. /// `fallback_name` names the file when the package is empty. pub fn merged_file_path(kotlin_root: &Path, file: &KtFile, fallback_name: &str) -> PathBuf { if file.package.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index b8974a2..cb620b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ mod types; mod tests; pub use code::KtCode; -pub use file::{merge_files, write_files, WriteKotlinError}; +pub use file::{merge_files, merged_file_path, write_files, WriteKotlinError}; pub use ident::{ escape_kotlin_ident, is_kotlin_hard_keyword, is_valid_kotlin_ident, is_valid_kotlin_package, mangle_kotlin_ident, mangle_kotlin_package, KOTLIN_HARD_KEYWORDS, @@ -32,5 +32,6 @@ pub use model::{ KtFile, KtFun, KtFunInterface, KtFunSig, KtParam, KtProperty, KtSuperclass, KtSupertypes, KtVis, }; +pub use render::KOTLIN_BANNER; pub use slot::KtPropertyValue; pub use types::{ImportSet, KtType}; diff --git a/src/render.rs b/src/render.rs index 3ff413b..a4d9fe1 100644 --- a/src/render.rs +++ b/src/render.rs @@ -10,7 +10,12 @@ use super::{ types::{ImportSet, KtType}, }; -/// First line of every generated file. +/// The default first line of every generated file — the "do not edit" marker +/// a reader needs to know the file is machine-written. +/// +/// [`KtFile::banner`] overrides it per file, and `""` suppresses it entirely; +/// this constant is what an override falls back to, and what a consumer +/// prepending its own header should match against. pub const KOTLIN_BANNER: &str = "// Auto-generated by kotlin-codegen — do not edit by hand."; /// When a function's single-line signature (from the indentation through the diff --git a/src/tests.rs b/src/tests.rs index d90d46a..491fb3b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -543,10 +543,10 @@ fn merge_files_groups_by_package_and_rejects_duplicates() { #[test] fn merged_file_path_is_flattened() { let f = KtFile::new("io.zenoh.jni.bytes"); - let p = file::merged_file_path(std::path::Path::new("/root"), &f, "X"); + let p = merged_file_path(std::path::Path::new("/root"), &f, "X"); assert_eq!(p, std::path::PathBuf::from("/root/io/zenoh/jni/bytes.kt")); let empty = KtFile::new(""); - let p2 = file::merged_file_path(std::path::Path::new("/root"), &empty, "NativeHandle"); + let p2 = merged_file_path(std::path::Path::new("/root"), &empty, "NativeHandle"); assert_eq!(p2, std::path::PathBuf::from("/root/NativeHandle.kt")); } @@ -927,3 +927,19 @@ fn external_and_a_body_are_mutually_exclusive() { fn external_cannot_be_passed_as_a_modifier_string() { let _ = KtFun::new("f").modifier("external"); } + +/// Both of these were `pub` in private modules — reachable from inside the +/// crate but not from a consumer. This test is written against the public +/// paths, so it fails to compile if either is un-exported again. +#[test] +fn banner_and_path_helper_are_reachable_from_the_crate_root() { + use crate as kotlin_codegen; + assert!(kotlin_codegen::KOTLIN_BANNER.starts_with("//")); + let f = KtFile::new("io.p"); + assert_eq!( + kotlin_codegen::merged_file_path(std::path::Path::new("/root"), &f, "X"), + std::path::PathBuf::from("/root/io/p.kt") + ); + // The default banner is what an un-overridden file actually renders. + assert!(f.render().starts_with(kotlin_codegen::KOTLIN_BANNER)); +}