use crate::backends::swift::gen_bindings::bridge_artifacts::umbrella_header;
use crate::backends::swift::naming::swift_source_ident;
use crate::codegen::shared::binding_fields;
use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{ApiSurface, PrimitiveType, TypeDef, TypeRef};
use crate::scaffold::naming::{swift_min_ios, swift_min_macos};
use crate::scaffold::{readme_language_configured, scaffold_meta};
use anyhow::Context as _;
use heck::ToLowerCamelCase;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
pub(crate) fn scaffold_swift(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let meta = scaffold_meta(config);
let (module, package_name) = (config.swift_module(), config.swift_package_name());
let min_macos_major = swift_min_macos(config).split('.').next().unwrap_or("13").to_string();
let min_ios_major = swift_min_ios(config).split('.').next().unwrap_or("16").to_string();
let crate_name = &config.name;
let binding_crate_name = format!("{crate_name}-swift");
let binding_crate_underscore = binding_crate_name.replace('-', "_");
let ffi_lib_name = config
.ffi
.as_ref()
.and_then(|f| f.lib_name.as_ref())
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{}_ffi", crate_name.replace('-', "_")));
let swift_capsule: Vec<(String, String, String)> = config
.swift
.as_ref()
.map(|c| {
let mut deps: Vec<(String, String, String)> = c
.capsule_types
.values()
.filter(|cap| !cap.package.is_empty())
.map(|cap| {
let product = crate::core::config::languages::zig_capsule_import_name(&cap.host_type)
.map(|s| s.to_string())
.unwrap_or_else(|| cap.host_type.clone());
(cap.package.clone(), cap.package_version.clone(), product)
})
.collect();
deps.sort();
deps.dedup();
deps
})
.unwrap_or_default();
let package_dependencies = if swift_capsule.is_empty() {
String::new()
} else {
let entries: Vec<String> = swift_capsule
.iter()
.map(|(pkg, ver, _product)| {
let ver_clause = if ver.is_empty() {
"branch: \"master\"".to_string()
} else {
format!("from: \"{ver}\"")
};
format!(" .package(url: \"{pkg}\", {ver_clause}),")
})
.collect();
format!("\n dependencies: [\n{}\n ],", entries.join("\n"))
};
let module_target_capsule_deps = if swift_capsule.is_empty() {
String::new()
} else {
let product_names: Vec<String> = swift_capsule
.iter()
.map(|(pkg, _ver, product)| {
let identity = pkg
.trim_end_matches('/')
.rsplit('/')
.next()
.unwrap_or(pkg)
.trim_end_matches(".git");
format!(", .product(name: \"{product}\", package: \"{identity}\")")
})
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect();
product_names.join("")
};
let package_swift = format!(
r#"// swift-tools-version: 6.0
import PackageDescription
import Foundation
// NOTE: Run `cargo build -p {binding_crate}` and then rerun `alef generate`
// before `swift build`. Alef materializes the swift-bridge Swift/C outputs into
// Sources/RustBridge and Sources/RustBridgeC when the Cargo build output exists.
// See README.md for the full workflow.
// Absolute path to the Cargo target dir, resolved from this manifest's own location so
// library resolution is independent of the process working directory (`swift test` may
// chdir into fixture dirs). `#filePath` is a compile-time literal, so computing this string
// performs no filesystem access.
let rustTargetDir = (#filePath as NSString).deletingLastPathComponent.appending("/../../target")
// Resolve the static archive for a Rust crate explicitly, preferring `release` over `debug`.
// `crates/{binding_crate}` and the FFI crate both build `crate-type = ["cdylib", "staticlib"]`,
// so `target/{{release,debug}}` holds both `lib<name>.a` and `lib<name>.dylib`. A bare
// `.linkedLibrary("<name>")` / `-l<name>` lets the linker pick between them, and ld64 prefers
// the `.dylib` when both sit in the same search directory — but that dylib was built with
// `-undefined dynamic_lookup` and does not itself define the swift-bridge glue symbols (e.g.
// `__swift_bridge__$<Type>$_free`), so the link silently succeeds while the resulting binary
// fails to resolve those symbols at dlopen/runtime. Passing the archive's resolved absolute
// path forces static linking unambiguously.
func resolvedStaticLib(_ name: String) -> String {{
let release = "\(rustTargetDir)/release/lib\(name).a"
let debug = "\(rustTargetDir)/debug/lib\(name).a"
return FileManager.default.fileExists(atPath: release) ? release : debug
}}
let package = Package(
name: "{package_name}",
platforms: [
.macOS(.v{min_macos}),
.iOS(.v{min_ios}),
],
products: [
.library(name: "{module}", targets: ["{module}"])
],{package_dependencies}
targets: [
// RustBridgeC: pure C/headers target. Swift files in RustBridge import this
// to access C types (RustStr, etc.) produced by swift-bridge.
// publicHeadersPath: "." exposes RustBridgeC.h to dependents.
.target(
name: "RustBridgeC",
path: "Sources/RustBridgeC",
publicHeadersPath: "."
),
// RustBridge: Swift wrapper around the Rust static library.
// Depends on RustBridgeC so the generated Swift files can use the C types.
// linkerSettings wire the Rust staticlibs (lib{binding_underscore}.a and lib{ffi_lib_name}.a)
// produced by `cargo build -p {binding_crate}` and the FFI crate so
// `swift build` / `swift test` can resolve the `__swift_bridge__$*` and FFI C symbols.
// Explicit absolute paths (see `resolvedStaticLib` above) are used instead of
// `.linkedLibrary(...)` so the linker cannot substitute the sibling `.dylib` artifacts.
// The FFI library is needed because the generated Swift service API code (App.swift)
// calls FFI functions directly via @_silgen_name declarations.
.target(
name: "RustBridge",
dependencies: ["RustBridgeC"],
path: "Sources/RustBridge",
linkerSettings: [
.unsafeFlags([
resolvedStaticLib("{binding_underscore}"),
resolvedStaticLib("{ffi_lib_name}"),
]),
// The Rust staticlib records native-library dependencies (e.g. `lzma-sys`
// via the archive/`xz2` path emits `cargo:rustc-link-lib`) that cargo would
// resolve when it drives the final link, but a `staticlib` `.a` does not
// embed them and SwiftPM does not read cargo's link metadata, so undefined
// symbols like `_lzma_stream_decoder` surface at the swift link step. Link
// the system library here. `liblzma` ships in the macOS SDK and on Linux.
.linkedLibrary("lzma"),
// Same staticlib-doesn't-embed-native-deps reasoning as lzma above: the
// bzip2 crates (archive/zip/unhwp paths) emit `-lbz2`, surfacing undefined
// `_BZ2_bzDecompress*` at the swift link step. `libbz2` ships in the macOS
// SDK and on Linux.
.linkedLibrary("bz2"),
// The Rust staticlib pulls in C++ dependencies (onnxruntime, tesseract,
// ClipperLib) that reference the C++ runtime/ABI (`__cxa_throw`,
// `__gxx_personality_v0`, `__cxa_guard_acquire`, ...). A `staticlib` `.a`
// does not carry the transitive `-lc++`/`-lstdc++` system-lib dependency,
// so SwiftPM must link the C++ standard library explicitly or the final
// link fails with undefined symbols from those crates.
.linkedLibrary("c++", .when(platforms: [.macOS, .iOS])),
.linkedLibrary("stdc++", .when(platforms: [.linux])),
.linkedFramework("Security", .when(platforms: [.macOS, .iOS])),
.linkedFramework("CoreFoundation", .when(platforms: [.macOS, .iOS])),
.linkedFramework("SystemConfiguration", .when(platforms: [.macOS])),
]
),
.target(
name: "{module}", dependencies: ["RustBridge"{module_target_capsule_deps}],
path: "Sources/{module}",
exclude: ["LICENSE"]),
.testTarget(
name: "{module}Tests", dependencies: ["{module}"],
path: "Tests/{module}Tests"),
]
)
"#,
module = module,
min_macos = min_macos_major,
min_ios = min_ios_major,
binding_crate = binding_crate_name,
binding_underscore = binding_crate_underscore,
ffi_lib_name = ffi_lib_name,
package_dependencies = package_dependencies,
module_target_capsule_deps = module_target_capsule_deps,
);
let gitignore = ".build/\nPackages/\nxcuserdata/\nDerivedData/\n.swiftpm/\n*.xcodeproj\n";
let test_stub = scaffold_swift_test(api, config, &module);
let rust_bridge_c_header = build_rust_bridge_c_header(&binding_crate_name)
.with_context(|| format!("building {RUST_BRIDGE_C_HEADER_PATH} for `{binding_crate_name}`"))?;
let rust_bridge_c_source = build_rust_bridge_c_source(&binding_crate_underscore);
let rust_bridge_swift = format!(
r#"// Placeholder Swift source for the RustBridge target.
// Run `cargo build -p {binding_crate}` and then rerun `alef generate` to replace
// this file with swift-bridge output. See README.md for instructions.
//
// This file is intentionally minimal so SwiftPM accepts the target before
// the cargo build step has been run.
public enum RustBridgePlaceholder {{}}
"#,
binding_crate = binding_crate_name,
);
let module_modulemap = "// This modulemap is unused — the RustBridgeC target provides the C types.\n// SwiftPM discovers RustBridgeC.h via the publicHeadersPath setting.\n";
let editorconfig = "[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\n\n[*.swift]\nindent_style = space\nindent_size = 2\n";
let swiftformat = "lineLength = 120\nindent = 2\nusesTabs = false\n";
let license_section = meta
.license
.as_deref()
.map(|license| format!("\n## License\n\n{license}\n"))
.unwrap_or_default();
let repository = config.github_repo();
let version = &api.version;
let readme = format!(
r#"# {module}
{description}
## Installation
Add to your `Package.swift`:
```swift
.package(url: "{repository}", from: "{version}"),
```
## Building
```sh
cargo build -p {binding_crate}
alef generate --lang swift
swift build --package-path packages/swift
swift test --package-path packages/swift
```
Before the Cargo build output exists, Alef emits placeholder RustBridge files so
the generated package layout is complete. After Cargo produces swift-bridge
artifacts, rerunning Alef replaces the placeholders with the generated Swift and
C bridge sources.
"#,
module = module,
description = meta.description,
binding_crate = binding_crate_name,
) + &license_section;
let demo_swift = format!(
r#"import {module}
@main
struct Demo {{
static func main() {{
print("Demo: {module} loaded successfully")
// Add your API calls here after code generation
}}
}}
"#,
module = module,
);
let root_package_swift = meta.repository.as_deref().map(|repository| {
format!(
r#"// swift-tools-version: 6.0
// Root-level Package.swift — alef-generated for published distributions.
//
// This manifest uses `.binaryTarget` for pre-built XCFramework/artifact bundles.
// External consumers depend on this via `.package(url: "...", from: "...")`.
//
// For in-tree development, see `packages/swift/Package.swift` and
// `packages/swift/README.md` for the source-based workflow.
import PackageDescription
let package = Package(
name: "{package_name}",
platforms: [
.macOS(.v{min_macos}),
.iOS(.v{min_ios}),
],
products: [
.library(name: "{module}", targets: ["{module}"])
],{package_dependencies}
targets: [
// RustBridgeC: C headers target. Swift files in RustBridge import this to
// access C types (RustStr, etc.) produced by swift-bridge.
// publicHeadersPath: "." exposes the headers.
.target(
name: "RustBridgeC",
path: "packages/swift/Sources/RustBridgeC",
publicHeadersPath: "."
),
// RustBridgeBinary: pre-built static library for macOS (arm64, x86_64),
// iOS (device, simulator), and Linux (arm64, x86_64). The artifactbundle
// ships `.a` files only — SwiftPM binary targets cannot supply Swift
// modules, so the swift-bridge generated Swift sources live in the
// sibling RustBridge target below and link against this binary.
.binaryTarget(
name: "RustBridgeBinary",
url: "{repository}/releases/download/v__ALEF_SWIFT_VERSION__/{module}-rs.artifactbundle.zip",
checksum: "__ALEF_SWIFT_CHECKSUM__"
),
// RustBridge: Swift wrapper module owning the swift-bridge generated
// sources. Depends on RustBridgeC for C type declarations and on
// RustBridgeBinary so the linker picks up the static library symbols.
.target(
name: "RustBridge",
dependencies: ["RustBridgeC", "RustBridgeBinary"],
path: "packages/swift/Sources/RustBridge",
// The pre-built static library inside RustBridgeBinary references Apple
// system frameworks (e.g. reqwest's proxy detection pulls in the Rust
// `system_configuration` crate → `SC*` symbols) and native system
// libraries (e.g. the archive/`xz2` path pulls in `lzma-sys` →
// `_lzma_stream_decoder`). The artifactbundle ships only the `.a`, so these
// must be linked by the consumer. `liblzma` ships in the macOS SDK and on
// Linux.
linkerSettings: [
.linkedLibrary("lzma"),
// Same reasoning as lzma: the bzip2 crates (archive/zip/unhwp) emit
// `-lbz2`, surfacing undefined `_BZ2_bzDecompress*`. `libbz2` ships in
// the macOS SDK and on Linux.
.linkedLibrary("bz2"),
// The pre-built static library pulls in C++ dependencies (onnxruntime,
// tesseract, ClipperLib) that reference the C++ runtime/ABI
// (`__cxa_throw`, `__gxx_personality_v0`, `__cxa_guard_acquire`, ...). A
// `.a` archive does not carry the transitive `-lc++`/`-lstdc++`
// system-lib dependency, so the consumer must link the C++ standard
// library explicitly or the final link fails with undefined symbols
// from those crates.
.linkedLibrary("c++", .when(platforms: [.macOS, .iOS])),
.linkedLibrary("stdc++", .when(platforms: [.linux])),
.linkedFramework("Security", .when(platforms: [.macOS, .iOS])),
.linkedFramework("CoreFoundation", .when(platforms: [.macOS, .iOS])),
.linkedFramework("SystemConfiguration", .when(platforms: [.macOS])),
]
),
.target(
name: "{module}",
dependencies: ["RustBridge", "RustBridgeC"{module_target_capsule_deps}],
path: "packages/swift/Sources/{module}"
),
]
)
"#,
module = module,
min_macos = min_macos_major,
min_ios = min_ios_major,
repository = repository.trim_end_matches('/'),
package_dependencies = package_dependencies,
module_target_capsule_deps = module_target_capsule_deps,
)
});
let mut files = vec![
GeneratedFile {
path: PathBuf::from("packages/swift/Package.swift"),
content: package_swift,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/.gitignore"),
content: gitignore.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from(format!("packages/swift/Tests/{module}Tests/{module}Tests.swift")),
content: test_stub,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridgeC/RustBridgeC.h"),
content: rust_bridge_c_header,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridgeC/RustBridgeC.c"),
content: rust_bridge_c_source,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridge/module.modulemap"),
content: module_modulemap.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridge/RustBridge.swift"),
content: rust_bridge_swift,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/.editorconfig"),
content: editorconfig.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/.swiftformat"),
content: swiftformat.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Examples/Demo/main.swift"),
content: demo_swift,
generated_header: false,
},
];
if !readme_language_configured(config, "swift") {
files.insert(
9,
GeneratedFile {
path: PathBuf::from("packages/swift/README.md"),
content: readme,
generated_header: false,
},
);
}
if let Some(root_package_swift) = root_package_swift {
files.insert(
0,
GeneratedFile {
path: PathBuf::from("Package.swift"),
content: root_package_swift,
generated_header: false,
},
);
}
Ok(files)
}
fn scaffold_swift_test(api: &ApiSurface, config: &ResolvedCrateConfig, module: &str) -> String {
let (exclude_types, exclude_fields) = swift_binding_exclusions(config);
let round_trip_candidate = api
.types
.iter()
.filter(|t| swift_type_is_visible(t, &exclude_types))
.filter(|t| !t.is_opaque && t.has_serde && !t.has_stripped_cfg_fields)
.find_map(|t| simple_codable_fields(t, &exclude_fields).map(|fields| (t, fields)));
if let Some((ty, fields)) = round_trip_candidate {
return codable_round_trip_test(module, &ty.name, &fields);
}
let visible_type_name = api
.types
.iter()
.filter(|t| swift_type_is_visible(t, &exclude_types))
.map(|t| t.name.clone())
.next();
let visible_enum_name = || {
api.enums
.iter()
.filter(|e| !e.binding_excluded && !exclude_types.contains(&e.name))
.map(|e| e.name.clone())
.next()
};
if let Some(name) = visible_type_name.or_else(visible_enum_name) {
return type_reference_test(module, &name);
}
placeholder_test(module)
}
pub(crate) fn migrate_swift_placeholder_test(
base_dir: &Path,
relative_path: &Path,
replacement: &str,
) -> anyhow::Result<bool> {
let path = crate::cli::pipeline::generate::write::contained_output_path(base_dir, relative_path)?;
let Ok(existing) = std::fs::read_to_string(&path) else {
return Ok(false);
};
if !is_vacuous_swift_placeholder(&existing) {
return Ok(false);
}
if existing == replacement {
return Ok(false);
}
let parent = path.parent().context("swift test path has no parent directory")?;
let mut temporary = tempfile::NamedTempFile::new_in(parent)
.with_context(|| format!("failed to create temporary file in {}", parent.display()))?;
std::io::Write::write_all(&mut temporary, replacement.as_bytes())
.with_context(|| format!("failed to write temporary file for {}", path.display()))?;
temporary
.persist(&path)
.map_err(|error| error.error)
.with_context(|| format!("failed to replace {}", path.display()))?;
tracing::info!(
path = %path.display(),
"repaired pre-existing Tests/*Tests.swift: replaced the vacuous XCTAssertTrue(true) \
placeholder with a real assertion against the generated API"
);
Ok(true)
}
fn is_vacuous_swift_placeholder(content: &str) -> bool {
(content.contains("XCTAssertTrue(true)") || content.contains("XCTAssert(true)"))
&& content.matches("XCTAssert").count() == 1
&& content.matches("func test").count() == 1
}
fn swift_binding_exclusions(config: &ResolvedCrateConfig) -> (HashSet<String>, HashSet<String>) {
let mut exclude_types: HashSet<String> = config
.swift
.as_ref()
.map(|c| c.exclude_types.iter().cloned().collect())
.unwrap_or_default();
let exclude_fields: HashSet<String> = config
.swift
.as_ref()
.map(|c| c.exclude_fields.iter().cloned().collect())
.unwrap_or_default();
if let Some(ffi) = &config.ffi {
exclude_types.extend(ffi.exclude_types.iter().cloned());
}
(exclude_types, exclude_fields)
}
fn swift_type_is_visible(ty: &TypeDef, exclude_types: &HashSet<String>) -> bool {
!ty.is_trait && !ty.binding_excluded && !exclude_types.contains(&ty.name)
}
struct SimpleCodableField {
swift_label: String,
literal: String,
}
fn simple_codable_fields(ty: &TypeDef, exclude_fields: &HashSet<String>) -> Option<Vec<SimpleCodableField>> {
let mut fields = Vec::new();
for field in binding_fields(&ty.fields) {
if field.optional || field.cfg.is_some() {
return None;
}
if exclude_fields.contains(&format!("{}.{}", ty.name, field.name)) {
return None;
}
let literal = match &field.ty {
TypeRef::Primitive(primitive) => primitive_literal(primitive),
TypeRef::String => "\"alef-scaffold\"".to_string(),
_ => return None,
};
fields.push(SimpleCodableField {
swift_label: swift_source_ident(&field.name.to_lower_camel_case()),
literal,
});
}
if fields.is_empty() { None } else { Some(fields) }
}
fn primitive_literal(primitive: &PrimitiveType) -> String {
match primitive {
PrimitiveType::Bool => "true".to_string(),
PrimitiveType::F32 | PrimitiveType::F64 => "1.5".to_string(),
_ => "1".to_string(),
}
}
fn codable_round_trip_test(module: &str, type_name: &str, fields: &[SimpleCodableField]) -> String {
let init_args = fields
.iter()
.map(|f| format!("{}: {}", f.swift_label, f.literal))
.collect::<Vec<_>>()
.join(", ");
format!(
"import XCTest\n\n\
@testable import {module}\n\n\
final class {module}Tests: XCTestCase {{\n \
/// Round-trips the generated `{type_name}` DTO through `JSONEncoder`/`JSONDecoder`,\n \
/// so a broken `Codable` conformance or a field that silently stops encoding fails\n \
/// `swift test` immediately instead of shipping green with a suite that asserts\n \
/// nothing about the generated API. Create-only scaffold seed. ~keep\n \
func test{type_name}RoundTripsThroughJSON() throws {{\n \
let original = {type_name}({init_args})\n \
let data = try JSONEncoder().encode(original)\n \
let decoded = try JSONDecoder().decode({type_name}.self, from: data)\n \
XCTAssertEqual(decoded, original)\n \
}}\n\
}}\n",
)
}
fn type_reference_test(module: &str, name: &str) -> String {
format!(
"import XCTest\n\n\
@testable import {module}\n\n\
final class {module}Tests: XCTestCase {{\n \
/// `{name}` isn't a literal-constructible Codable DTO this seed can safely\n \
/// round-trip generically, so this checks the generated type exists and is\n \
/// referenceable at compile time instead. Create-only scaffold seed. ~keep\n \
func test{name}Exists() {{\n \
XCTAssertNotNil({name}.self)\n \
}}\n\
}}\n",
)
}
fn placeholder_test(module: &str) -> String {
format!(
"import XCTest\n\n\
@testable import {module}\n\n\
final class {module}Tests: XCTestCase {{\n \
/// No generated API surface exists yet for this crate, so there is nothing to\n \
/// assert against beyond the module resolving. Once real types exist, alef never\n \
/// regenerates over this file -- it is a create-only scaffold seed. ~keep\n \
func testModuleImportsSuccessfully() throws {{\n \
XCTAssertTrue(true)\n \
}}\n\
}}\n",
)
}
const RUST_BRIDGE_C_HEADER_PATH: &str = "packages/swift/Sources/RustBridgeC/RustBridgeC.h";
fn build_rust_bridge_c_header(binding_crate_name: &str) -> anyhow::Result<String> {
let fresh_headers = read_swift_bridge_headers(binding_crate_name);
let existing_header = std::fs::read_to_string(RUST_BRIDGE_C_HEADER_PATH).ok();
render_rust_bridge_c_header(binding_crate_name, fresh_headers, existing_header.as_deref())
}
fn render_rust_bridge_c_header(
binding_crate_name: &str,
fresh_headers: Option<(String, String)>,
existing_header: Option<&str>,
) -> anyhow::Result<String> {
if let Some((core_h, crate_h)) = fresh_headers {
return umbrella_header::resolve_fresh(binding_crate_name, &core_h, &crate_h, existing_header);
}
if let Some(existing) = existing_header
&& umbrella_header::is_populated(existing)
{
return Ok(existing.to_string());
}
Ok(format!(
"#ifndef RUST_BRIDGE_C_H\n\
#define RUST_BRIDGE_C_H\n\
\n\
// Placeholder header for the RustBridgeC SwiftPM target.\n\
// Run `cargo build -p {binding_crate_name}` and re-run `alef all` to populate.\n\
// The typedefs below are the minimum required for SwiftBridgeCore.swift\n\
// to compile before the full cargo build has been run.\n\
\n\
#include <stdbool.h>\n\
#include <stdint.h>\n\
\n\
typedef struct RustStr {{\n \
uint8_t *const start;\n \
uintptr_t len;\n\
}} RustStr;\n\
typedef struct __private__FfiSlice {{\n \
void *const start;\n \
uintptr_t len;\n\
}} __private__FfiSlice;\n\
typedef struct __private__OptionU8 {{\n \
uint8_t val;\n \
bool is_some;\n\
}} __private__OptionU8;\n\
typedef struct __private__OptionI8 {{\n \
int8_t val;\n \
bool is_some;\n\
}} __private__OptionI8;\n\
typedef struct __private__OptionU16 {{\n \
uint16_t val;\n \
bool is_some;\n\
}} __private__OptionU16;\n\
typedef struct __private__OptionI16 {{\n \
int16_t val;\n \
bool is_some;\n\
}} __private__OptionI16;\n\
typedef struct __private__OptionU32 {{\n \
uint32_t val;\n \
bool is_some;\n\
}} __private__OptionU32;\n\
typedef struct __private__OptionI32 {{\n \
int32_t val;\n \
bool is_some;\n\
}} __private__OptionI32;\n\
typedef struct __private__OptionU64 {{\n \
uint64_t val;\n \
bool is_some;\n\
}} __private__OptionU64;\n\
typedef struct __private__OptionI64 {{\n \
int64_t val;\n \
bool is_some;\n\
}} __private__OptionI64;\n\
typedef struct __private__OptionUsize {{\n \
uintptr_t val;\n \
bool is_some;\n\
}} __private__OptionUsize;\n\
typedef struct __private__OptionIsize {{\n \
intptr_t val;\n \
bool is_some;\n\
}} __private__OptionIsize;\n\
typedef struct __private__OptionF32 {{\n \
float val;\n \
bool is_some;\n\
}} __private__OptionF32;\n\
typedef struct __private__OptionF64 {{\n \
double val;\n \
bool is_some;\n\
}} __private__OptionF64;\n\
typedef struct __private__OptionBool {{\n \
bool val;\n \
bool is_some;\n\
}} __private__OptionBool;\n\
\n\
#endif /* RUST_BRIDGE_C_H */\n"
))
}
fn build_rust_bridge_c_source(binding_crate_underscore: &str) -> String {
format!(
"#include \"RustBridgeC.h\"\n\
\n\
// ~keep anchor TU so XCBuild emits RustBridgeC.o (issue #449)\n\
void {binding_crate_underscore}_rust_bridge_c_anchor(void) {{}}\n"
)
}
fn read_swift_bridge_headers(binding_crate_name: &str) -> Option<(String, String)> {
let cwd = std::env::current_dir().ok()?;
let workspace_root = std::iter::once(cwd.clone())
.chain(cwd.ancestors().skip(1).map(|p| p.to_path_buf()))
.take(8)
.find(|p| p.join("Cargo.lock").exists())?;
let target = workspace_root.join("target");
let crate_prefix = format!("{}-", binding_crate_name);
let mut best: Option<(std::time::SystemTime, PathBuf)> = None;
for profile in ["release", "debug"] {
let build_dir = target.join(profile).join("build");
let entries = match std::fs::read_dir(&build_dir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if !name_str.starts_with(&crate_prefix) {
continue;
}
let out = entry.path().join("out");
let core_h = out.join("SwiftBridgeCore.h");
let crate_h = out.join(binding_crate_name).join(format!("{binding_crate_name}.h"));
if !core_h.exists() || !crate_h.exists() {
continue;
}
let mtime = std::fs::metadata(&core_h)
.and_then(|m| m.modified())
.unwrap_or(std::time::UNIX_EPOCH);
if best.as_ref().map(|(t, _)| mtime > *t).unwrap_or(true) {
best = Some((mtime, out));
}
}
}
let out = best?.1;
let core_h = std::fs::read_to_string(out.join("SwiftBridgeCore.h")).ok()?;
let crate_h = std::fs::read_to_string(out.join(binding_crate_name).join(format!("{binding_crate_name}.h"))).ok()?;
Some((core_h, crate_h))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::config::NewAlefConfig;
use crate::core::ir::ApiSurface;
fn resolve_config(toml_text: &str) -> ResolvedCrateConfig {
let cfg: NewAlefConfig = toml::from_str(toml_text).expect("valid config");
cfg.resolve().expect("resolve").remove(0)
}
fn find_file<'a>(files: &'a [GeneratedFile], path: &str) -> &'a GeneratedFile {
files
.iter()
.find(|f| f.path == std::path::Path::new(path))
.unwrap_or_else(|| panic!("missing scaffolded file: {path}"))
}
fn simple_type(name: &str) -> TypeDef {
TypeDef {
name: name.to_string(),
has_serde: true,
fields: vec![
crate::core::ir::FieldDef {
name: "count".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
..Default::default()
},
crate::core::ir::FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
..Default::default()
},
],
..Default::default()
}
}
#[test]
fn scaffold_test_round_trips_a_simple_dto() {
let api = ApiSurface {
types: vec![simple_type("Widget")],
..Default::default()
};
let out = scaffold_swift_test(&api, &minimal_config(), "MyLib");
assert!(out.contains("@testable import MyLib"), "got:\n{out}");
assert!(
out.contains("func testWidgetRoundTripsThroughJSON() throws {"),
"got:\n{out}"
);
assert!(
out.contains("let original = Widget(count: 1, label: \"alef-scaffold\")"),
"got:\n{out}"
);
assert!(out.contains("try JSONEncoder().encode(original)"), "got:\n{out}");
assert!(
out.contains("try JSONDecoder().decode(Widget.self, from: data)"),
"got:\n{out}"
);
assert!(out.contains("XCTAssertEqual(decoded, original)"), "got:\n{out}");
assert!(
!out.contains("XCTAssertTrue(true)"),
"must not be the vacuous placeholder, got:\n{out}"
);
}
#[test]
fn scaffold_test_falls_back_to_existence_check_for_an_opaque_type() {
let api = ApiSurface {
types: vec![TypeDef {
name: "Client".to_string(),
is_opaque: true,
..Default::default()
}],
..Default::default()
};
let out = scaffold_swift_test(&api, &minimal_config(), "MyLib");
assert!(out.contains("func testClientExists() {"), "got:\n{out}");
assert!(out.contains("XCTAssertNotNil(Client.self)"), "got:\n{out}");
assert!(!out.contains("JSONEncoder"), "got:\n{out}");
}
#[test]
fn scaffold_test_falls_back_to_existence_check_for_unsupported_field_shape() {
let api = ApiSurface {
types: vec![TypeDef {
name: "Config".to_string(),
has_serde: true,
fields: vec![crate::core::ir::FieldDef {
name: "nickname".to_string(),
ty: TypeRef::Optional(Box::new(TypeRef::String)),
optional: true,
..Default::default()
}],
..Default::default()
}],
..Default::default()
};
let out = scaffold_swift_test(&api, &minimal_config(), "MyLib");
assert!(out.contains("func testConfigExists() {"), "got:\n{out}");
assert!(out.contains("XCTAssertNotNil(Config.self)"), "got:\n{out}");
}
#[test]
fn scaffold_test_falls_back_to_existence_check_for_an_enum_when_no_types_exist() {
let api = ApiSurface {
enums: vec![crate::core::ir::EnumDef {
name: "Color".to_string(),
..Default::default()
}],
..Default::default()
};
let out = scaffold_swift_test(&api, &minimal_config(), "MyLib");
assert!(out.contains("func testColorExists() {"), "got:\n{out}");
assert!(out.contains("XCTAssertNotNil(Color.self)"), "got:\n{out}");
}
#[test]
fn scaffold_test_skips_binding_excluded_types() {
let api = ApiSurface {
types: vec![
TypeDef {
name: "Hidden".to_string(),
is_opaque: true,
binding_excluded: true,
..Default::default()
},
TypeDef {
name: "Visible".to_string(),
is_opaque: true,
..Default::default()
},
],
..Default::default()
};
let out = scaffold_swift_test(&api, &minimal_config(), "MyLib");
assert!(out.contains("Visible"), "got:\n{out}");
assert!(!out.contains("Hidden"), "got:\n{out}");
}
#[test]
fn scaffold_test_falls_back_to_placeholder_when_api_surface_is_empty() {
let out = scaffold_swift_test(&ApiSurface::default(), &minimal_config(), "MyLib");
assert!(out.contains("@testable import MyLib"), "got:\n{out}");
assert!(
out.contains("func testModuleImportsSuccessfully() throws {"),
"got:\n{out}"
);
assert!(out.contains("XCTAssertTrue(true)"), "got:\n{out}");
}
#[test]
fn scaffold_swift_emits_real_test_assertions_and_is_create_only() {
let api = ApiSurface {
types: vec![simple_type("Widget")],
..Default::default()
};
let files = scaffold_swift(&api, &minimal_config()).expect("scaffold");
let test_file = find_file(&files, "packages/swift/Tests/MyLibTests/MyLibTests.swift");
assert!(
!test_file.generated_header,
"test seed must be generated_header: false (create-only)"
);
assert!(test_file.content.contains("JSONEncoder"), "got:\n{}", test_file.content);
assert!(
!test_file.content.contains("func testPlaceholder"),
"must not emit the old vacuous placeholder test, got:\n{}",
test_file.content
);
}
#[test]
fn scaffold_swift_always_emits_a_test_file_seed() {
let files = scaffold_swift(&ApiSurface::default(), &minimal_config()).expect("scaffold");
let test_file = find_file(&files, "packages/swift/Tests/MyLibTests/MyLibTests.swift");
assert!(test_file.content.contains("@testable import MyLib"));
}
fn minimal_config() -> ResolvedCrateConfig {
resolve_config(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "my-lib"
sources = []
"#,
)
}
#[test]
fn render_header_prefers_fresh_build_output() {
let out = render_rust_bridge_c_header(
"my-lib-swift",
Some((
"// core\nvoid __swift_bridge__$core(void);\n".into(),
"// crate\n".into(),
)),
Some("// stale __swift_bridge__$old\n"),
)
.expect("a complete assembly must resolve");
assert!(
out.contains("Concatenates SwiftBridgeCore.h"),
"expected umbrella, got:\n{out}"
);
assert!(
out.contains("__swift_bridge__$core"),
"expected fresh core decls, got:\n{out}"
);
}
#[test]
fn render_header_preserves_committed_populated_header() {
let populated = "#include <stdint.h>\nvoid __swift_bridge__$RustStr$partial_eq(void);\n";
let out = render_rust_bridge_c_header("my-lib-swift", None, Some(populated)).expect("render");
assert_eq!(
out, populated,
"populated header must be preserved verbatim when no fresh output"
);
}
#[test]
fn render_header_preserves_markerless_populated_header() {
let populated = "#include <stdint.h>\nvoid __swift_bridge__$Vec_u8$new(void);\n";
assert!(umbrella_header::is_populated(populated));
let out = render_rust_bridge_c_header("my-lib-swift", None, Some(populated)).expect("render");
assert_eq!(out, populated);
}
#[test]
fn render_header_emits_placeholder_without_populated_source() {
let placeholder_marker = "Placeholder header for the RustBridgeC SwiftPM target";
let from_nothing = render_rust_bridge_c_header("my-lib-swift", None, None).expect("render");
assert!(
from_nothing.contains(placeholder_marker),
"expected placeholder, got:\n{from_nothing}"
);
assert!(
!umbrella_header::is_populated(&from_nothing),
"placeholder must not look populated"
);
let stale_placeholder = "#ifndef RUST_BRIDGE_C_H\ntypedef struct RustStr { int x; } RustStr;\n";
assert!(!umbrella_header::is_populated(stale_placeholder));
let from_placeholder =
render_rust_bridge_c_header("my-lib-swift", None, Some(stale_placeholder)).expect("render");
assert!(
from_placeholder.contains(placeholder_marker),
"expected placeholder, got:\n{from_placeholder}"
);
}
#[test]
fn root_package_swift_uses_binary_target_with_placeholders() {
let config = resolve_config(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "my-lib"
sources = []
[crates.package_metadata]
repository = "https://github.com/example/my-lib"
"#,
);
let api = ApiSurface::default();
let files = scaffold_swift(&api, &config).expect("scaffold");
let root = find_file(&files, "Package.swift");
assert!(
root.content.contains(".binaryTarget("),
"root Package.swift must use .binaryTarget, got:\n{}",
root.content
);
assert!(
!root.content.contains(".unsafeFlags"),
"root Package.swift must not contain .unsafeFlags (breaks remote SwiftPM consumers), got:\n{}",
root.content
);
assert!(
root.content.contains("v__ALEF_SWIFT_VERSION__"),
"root Package.swift must contain __ALEF_SWIFT_VERSION__ placeholder for sync-versions, got:\n{}",
root.content
);
assert!(
root.content.contains("__ALEF_SWIFT_CHECKSUM__"),
"root Package.swift must contain __ALEF_SWIFT_CHECKSUM__ placeholder for publish flow, got:\n{}",
root.content
);
assert!(
root.content
.contains("https://github.com/example/my-lib/releases/download/v__ALEF_SWIFT_VERSION__/"),
"root Package.swift URL must point at configured repository, got:\n{}",
root.content
);
assert!(
root.content.contains("RustBridgeC"),
"root Package.swift must declare RustBridgeC target for C types, got:\n{}",
root.content
);
assert!(
root.content.contains(r#"dependencies: ["RustBridge", "RustBridgeC"]"#),
"root Package.swift must declare bridge dependencies for the Swift target, got:\n{}",
root.content
);
}
#[test]
fn in_tree_package_swift_keeps_source_based_layout() {
let config = resolve_config(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "my-lib"
sources = []
"#,
);
let api = ApiSurface::default();
let files = scaffold_swift(&api, &config).expect("scaffold");
let pkg = find_file(&files, "packages/swift/Package.swift");
assert!(
pkg.content.contains(".unsafeFlags"),
"in-tree packages/swift/Package.swift must keep source-based layout"
);
assert!(
!pkg.content.contains(".binaryTarget("),
"in-tree packages/swift/Package.swift must not use .binaryTarget"
);
}
#[test]
fn package_swift_links_cxx_standard_library_per_platform() {
let config = resolve_config(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "my-lib"
sources = []
[crates.package_metadata]
repository = "https://github.com/example/my-lib"
"#,
);
let api = ApiSurface::default();
let files = scaffold_swift(&api, &config).expect("scaffold");
let in_tree = find_file(&files, "packages/swift/Package.swift");
assert!(
in_tree
.content
.contains(r#".linkedLibrary("c++", .when(platforms: [.macOS, .iOS]))"#),
"in-tree Package.swift must link libc++ on Apple platforms, got:\n{}",
in_tree.content
);
assert!(
in_tree
.content
.contains(r#".linkedLibrary("stdc++", .when(platforms: [.linux]))"#),
"in-tree Package.swift must link libstdc++ on Linux, got:\n{}",
in_tree.content
);
let root = find_file(&files, "Package.swift");
assert!(
root.content
.contains(r#".linkedLibrary("c++", .when(platforms: [.macOS, .iOS]))"#),
"root Package.swift must link libc++ on Apple platforms, got:\n{}",
root.content
);
assert!(
root.content
.contains(r#".linkedLibrary("stdc++", .when(platforms: [.linux]))"#),
"root Package.swift must link libstdc++ on Linux, got:\n{}",
root.content
);
}
#[test]
fn in_tree_package_swift_with_capsules_has_correct_argument_order() {
let config = resolve_config(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "my-lib"
sources = []
[crates.swift.capsule_types.Language]
host_type = "SwiftTreeSitter.Language"
package = "https://github.com/tree-sitter/tree-sitter-swift"
package_version = "0.25.0"
"#,
);
let api = ApiSurface::default();
let files = scaffold_swift(&api, &config).expect("scaffold");
let pkg = find_file(&files, "packages/swift/Package.swift");
let products_pos = pkg.content.find("products: [").expect("must have products: argument");
let dependencies_pos = pkg
.content
.find("dependencies: [")
.expect("must have dependencies: argument when capsules present");
assert!(
products_pos < dependencies_pos,
"products: must precede dependencies: in Package(...) initializer. \
Found products: at byte {}, dependencies: at byte {}. Full content:\n{}",
products_pos,
dependencies_pos,
pkg.content
);
assert!(
pkg.content.contains("tree-sitter-swift"),
"capsule package reference must be present in dependencies: block"
);
assert!(
pkg.content.contains("0.25.0"),
"capsule package version must be present in dependencies: block"
);
}
#[test]
fn root_package_swift_injects_capsule_dependencies() {
let config = resolve_config(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "my-lib"
sources = []
[crates.scaffold]
repository = "https://github.com/acme/my-lib"
[crates.swift.capsule_types.Language]
host_type = "SwiftTreeSitter.Language"
package = "https://github.com/tree-sitter/swift-tree-sitter"
package_version = "0.25.0"
"#,
);
let api = ApiSurface::default();
let files = scaffold_swift(&api, &config).expect("scaffold");
let pkg = find_file(&files, "Package.swift");
assert!(
pkg.content
.contains(".package(url: \"https://github.com/tree-sitter/swift-tree-sitter\""),
"root manifest must declare the capsule package dependency. Full content:\n{}",
pkg.content
);
assert!(
pkg.content
.contains(".product(name: \"SwiftTreeSitter\", package: \"swift-tree-sitter\")"),
"root manifest module target must depend on the capsule product. Full content:\n{}",
pkg.content
);
}
fn h2m_historical_placeholder() -> &'static str {
r##"import XCTest
@testable import HtmlToMarkdown
final class HtmlToMarkdownTests: XCTestCase {
func testPlaceholder() throws {
// Placeholder test so `swift test` has a target to run.
// Replace or extend with real tests against the HtmlToMarkdown module.
XCTAssertTrue(true)
}
}
"##
}
fn liter_llm_hand_written_suite() -> &'static str {
r##"import XCTest
@testable import LiterLlm
final class LiterLlmTests: XCTestCase {
func testSystemMessageRoundTripsThroughJSON() throws {
let message = SystemMessage(content: .text(field0: "be concise"), name: "system")
let data = try JSONEncoder().encode(message)
let decoded = try JSONDecoder().decode(SystemMessage.self, from: data)
XCTAssertEqual(decoded, message)
}
}
"##
}
fn tree_sitter_language_pack_hand_written_suite() -> &'static str {
r##"import XCTest
import Foundation
import TreeSitterLanguagePack
import SwiftTreeSitter
// Requires the native `tree-sitter-language-pack-swift` + `ts-pack-core-ffi` Rust crates to
// be built and wired via `scripts/setup-swift-bridge.sh` (see `task swift:build` /
// `task swift:test`), compiled with TSLP_LINK_MODE=static and TSLP_LANGUAGES=mojo,nim,norg
// (see .task/swift.yml). Every test below that needs a real grammar (parsing, root-node
// kind, `process()`) uses "nim", one of those three statically-compiled languages, so this
// suite needs no network access and no warm download cache. "python"/"rust"/"markdown"
// appear only as literal data values in the pure language-detection tests below, which
// consult a static extension/shebang lookup table and never touch a parser.
/// Pure functions that need no grammar at all: extension/path/shebang lookup tables.
final class LanguageDetectionTests: XCTestCase {
func testDetectLanguageFromExtensionMapsPyToPython() {
XCTAssertEqual(
TreeSitterLanguagePack.detectLanguageFromExtension(ext: "py"),
"python",
"\"py\" is a well-known extension and must resolve to \"python\""
)
}
func testDetectLanguageFromExtensionIsCaseInsensitive() {
XCTAssertEqual(
TreeSitterLanguagePack.detectLanguageFromExtension(ext: "RS"),
"rust",
"extension matching must be case-insensitive per documented behavior"
)
}
func testDetectLanguageFromExtensionReturnsNilForUnknownExtension() {
XCTAssertNil(TreeSitterLanguagePack.detectLanguageFromExtension(ext: "this-extension-does-not-exist"))
}
func testDetectLanguageFromPathMatchesRustFile() {
XCTAssertEqual(TreeSitterLanguagePack.detectLanguageFromPath(path: "src/main.rs"), "rust")
}
func testDetectLanguageFromPathReturnsNilWithoutExtension() {
XCTAssertNil(
TreeSitterLanguagePack.detectLanguageFromPath(path: "Makefile"),
"a path with no extension has nothing to detect from"
)
}
func testDetectLanguageFromContentMatchesPythonShebang() {
XCTAssertEqual(
TreeSitterLanguagePack.detectLanguageFromContent(content: "#!/usr/bin/env python3\npass"),
"python"
)
}
func testDetectLanguageFromContentReturnsNilWithoutShebang() {
XCTAssertNil(TreeSitterLanguagePack.detectLanguageFromContent(content: "no shebang here"))
}
func testDetectLanguageAliasResolvesPathExtension() {
XCTAssertEqual(
TreeSitterLanguagePack.detectLanguage(path: "README.md"),
"markdown",
"detectLanguage is documented as a path/extension detection alias"
)
}
}
/// Bundled query lookup: also pure, no grammar required.
final class BundledQueryTests: XCTestCase {
func testGetHighlightsQueryReturnsNilForUnknownLanguage() {
XCTAssertNil(TreeSitterLanguagePack.getHighlightsQuery(language: "this-language-does-not-exist"))
}
}
/// Registry checks against "nim", which `task swift:test` compiles in statically
/// (TSLP_LANGUAGES=mojo,nim,norg), so these never touch the network.
final class RegistryTests: XCTestCase {
func testHasLanguageIsTrueForStaticallyCompiledLanguage() {
XCTAssertTrue(
TreeSitterLanguagePack.hasLanguage(name: "nim"),
"nim is compiled in by the swift build task (TSLP_LANGUAGES=mojo,nim,norg)"
)
}
func testHasLanguageIsFalseForUnknownLanguage() {
XCTAssertFalse(TreeSitterLanguagePack.hasLanguage(name: "totally-bogus-language-name"))
}
func testAvailableLanguagesContainsStaticallyCompiledLanguage() {
XCTAssertTrue(TreeSitterLanguagePack.availableLanguages().contains("nim"))
}
func testLanguageCountMatchesAvailableLanguagesCount() {
let count = TreeSitterLanguagePack.languageCount()
let names = TreeSitterLanguagePack.availableLanguages()
XCTAssertEqual(
count,
UInt(names.count),
"languageCount() must always equal availableLanguages().count; a mismatch means one of the two "
+ "accessors is stale relative to the other"
)
}
}
/// Real parsing through the upstream SwiftTreeSitter.Parser, mirroring
/// CapsulePassthroughTests.swift's hand-written pattern: `getLanguage()` hands back a real
/// `SwiftTreeSitter.Language` capsule usable directly with `SwiftTreeSitter.Parser`.
///
/// Uses "nim" (statically compiled, see the file-header comment) rather than "python": the
/// upstream e2e CapsulePassthroughTests.swift precedent parses python, but that grammar is
/// not one of TSLP_LANGUAGES=mojo,nim,norg baked into this package's build, so it would make
/// this suite network-dependent.
final class ParsingTests: XCTestCase {
// parsers/nim/src/grammar.json names its start rule "module", and node-types.json
// confirms "module" is a named node type — verified directly against the grammar
// sources vendored in this repo, not assumed from another language's doc example.
func testGetLanguageProducesAParserUsableNimLanguage() throws {
let nimLanguage = try TreeSitterLanguagePack.getLanguage(name: "nim")
var parser = Parser()
try parser.setLanguage(nimLanguage)
let tree = try XCTUnwrap(parser.parse("echo \"hello\""), "parsing valid nim source must produce a tree")
let root = try XCTUnwrap(tree.rootNode)
XCTAssertEqual(root.nodeType, "module", "nim's tree-sitter grammar names its root node \"module\"")
}
// fixtures/smoke/nim.json asserts `not_error` for this exact source, so it is
// known-valid nim, not a guess.
func testProcessEchoesBackTheRequestedLanguageForValidNim() throws {
let configObj = try TreeSitterLanguagePack.processConfigFromJson("{\"language\":\"nim\"}")
let result = try TreeSitterLanguagePack.process(source: "echo \"hello\"", config: configObj)
XCTAssertEqual(result.language().toString(), "nim")
}
// No structure-extraction test: crates/ts-pack-core/src/intel/intelligence.rs
// `structure_kind_at()` matches an exact, hardcoded set of tree-sitter node kind names
// (`function_definition`, `function_item`, `struct_item`, ...), and nim's grammar
// (parsers/nim/src/node-types.json) uses none of them — its declarations are named
// `declaration` / `declColonEquals`. Structure extraction is therefore unimplemented for
// nim and would always report an empty list; asserting that would be vacuous, so the
// test is omitted rather than weakened to a truthiness check.
}
/// Error paths: unknown languages and invalid configuration must fail loudly, never
/// silently succeed with garbage output.
final class ErrorHandlingTests: XCTestCase {
func testGetLanguageThrowsForUnknownLanguage() {
XCTAssertThrowsError(try TreeSitterLanguagePack.getLanguage(name: "this-language-does-not-exist-anywhere"))
}
func testProcessThrowsForEmptyLanguageName() throws {
let configObj = try TreeSitterLanguagePack.processConfigFromJson("{\"language\":\"\"}")
XCTAssertThrowsError(try TreeSitterLanguagePack.process(source: "hello", config: configObj))
}
}
"##
}
#[test]
fn vacuity_signature_matches_real_consumer_trees() {
assert!(is_vacuous_swift_placeholder(h2m_historical_placeholder()));
assert!(!is_vacuous_swift_placeholder(liter_llm_hand_written_suite()));
assert!(!is_vacuous_swift_placeholder(
tree_sitter_language_pack_hand_written_suite()
));
}
#[test]
fn migrates_the_current_generators_placeholder_output() {
let dir = tempfile::tempdir().expect("tempdir");
let test_dir = dir.path().join("packages/swift/Tests/MyLibTests");
std::fs::create_dir_all(&test_dir).expect("create Tests/MyLibTests");
let current_placeholder = placeholder_test("MyLib");
std::fs::write(test_dir.join("MyLibTests.swift"), ¤t_placeholder).expect("write current placeholder");
let replacement = codable_round_trip_test(
"MyLib",
"Widget",
&[SimpleCodableField {
swift_label: "count".to_string(),
literal: "1".to_string(),
}],
);
let relative_path = std::path::Path::new("packages/swift/Tests/MyLibTests/MyLibTests.swift");
let changed =
migrate_swift_placeholder_test(dir.path(), relative_path, &replacement).expect("migration must not error");
assert!(
changed,
"the current generator's own placeholder must be reported as changed"
);
let on_disk = std::fs::read_to_string(test_dir.join("MyLibTests.swift")).expect("read migrated file");
assert_eq!(on_disk, replacement);
}
#[test]
fn migrates_the_h2m_historical_shape_and_is_idempotent() {
let dir = tempfile::tempdir().expect("tempdir");
let test_dir = dir.path().join("packages/swift/Tests/HtmlToMarkdownTests");
std::fs::create_dir_all(&test_dir).expect("create Tests/HtmlToMarkdownTests");
std::fs::write(test_dir.join("HtmlToMarkdownTests.swift"), h2m_historical_placeholder())
.expect("write h2m historical placeholder");
let replacement = codable_round_trip_test(
"HtmlToMarkdown",
"Widget",
&[SimpleCodableField {
swift_label: "count".to_string(),
literal: "1".to_string(),
}],
);
let relative_path = std::path::Path::new("packages/swift/Tests/HtmlToMarkdownTests/HtmlToMarkdownTests.swift");
let changed =
migrate_swift_placeholder_test(dir.path(), relative_path, &replacement).expect("migration must not error");
assert!(
changed,
"h2m's real on-disk placeholder shape must be reported as changed"
);
let on_disk = std::fs::read_to_string(test_dir.join("HtmlToMarkdownTests.swift")).expect("read migrated file");
assert_eq!(on_disk, replacement);
let changed_again = migrate_swift_placeholder_test(dir.path(), relative_path, &replacement)
.expect("second pass must not error");
assert!(
!changed_again,
"second pass over an already-migrated file must be a no-op"
);
}
#[test]
fn does_not_touch_the_liter_llm_hand_written_suite() {
let dir = tempfile::tempdir().expect("tempdir");
let test_dir = dir.path().join("packages/swift/Tests/LiterLlmTests");
std::fs::create_dir_all(&test_dir).expect("create Tests/LiterLlmTests");
let hand_written = liter_llm_hand_written_suite();
std::fs::write(test_dir.join("LiterLlmTests.swift"), hand_written).expect("write hand-written test");
let relative_path = std::path::Path::new("packages/swift/Tests/LiterLlmTests/LiterLlmTests.swift");
let changed = migrate_swift_placeholder_test(dir.path(), relative_path, "anything else entirely")
.expect("migration must not error");
assert!(!changed, "a real suite must never be reported as changed");
let on_disk = std::fs::read_to_string(test_dir.join("LiterLlmTests.swift")).expect("read file");
assert_eq!(on_disk, hand_written, "a real suite must survive byte-for-byte");
}
#[test]
fn does_not_touch_the_tree_sitter_language_pack_hand_written_suite() {
let dir = tempfile::tempdir().expect("tempdir");
let test_dir = dir.path().join("packages/swift/Tests/TreeSitterLanguagePackTests");
std::fs::create_dir_all(&test_dir).expect("create Tests/TreeSitterLanguagePackTests");
let hand_written = tree_sitter_language_pack_hand_written_suite();
std::fs::write(test_dir.join("TreeSitterLanguagePackTests.swift"), hand_written)
.expect("write hand-written test");
let relative_path =
std::path::Path::new("packages/swift/Tests/TreeSitterLanguagePackTests/TreeSitterLanguagePackTests.swift");
let changed = migrate_swift_placeholder_test(dir.path(), relative_path, "anything else entirely")
.expect("migration must not error");
assert!(!changed, "a hand-written suite must never be reported as changed");
let on_disk = std::fs::read_to_string(test_dir.join("TreeSitterLanguagePackTests.swift")).expect("read file");
assert_eq!(on_disk, hand_written, "hand-written suite must survive byte-for-byte");
}
#[test]
fn migrate_swift_placeholder_is_a_no_op_when_file_does_not_exist() {
let dir = tempfile::tempdir().expect("tempdir");
let relative_path = std::path::Path::new("packages/swift/Tests/MyLibTests/MyLibTests.swift");
let changed = migrate_swift_placeholder_test(dir.path(), relative_path, "new content").expect("must not error");
assert!(!changed);
assert!(!dir.path().join(relative_path).exists());
}
}