macroforge_ts 0.1.79

TypeScript macro expansion engine - write compile-time macros in Rust
Documentation
//! # Patch Application Engine
//!
//! This module provides the core patch application functionality that transforms
//! source code according to patches generated by macros. It supports multiple
//! patch types and generates bidirectional source mappings for IDE integration.
//!
//! ## Patch Types
//!
//! The engine supports five patch operations:
//!
//! | Type | Description |
//! |------|-------------|
//! | `Insert` | Insert formatted AST node at position |
//! | `InsertRaw` | Insert raw text at position |
//! | `Replace` | Replace span with formatted AST node |
//! | `ReplaceRaw` | Replace span with raw text |
//! | `Delete` | Remove span entirely |
//!
//! ## Application Strategy
//!
//! Patches are sorted by position and applied in forward order, with the
//! source mapping tracking position shifts:
//!
//! ```text
//! Original:  "class Foo {}"
//!                      ^ Insert " bar: string;" at position 11
//!
//! Expanded:  "class Foo { bar: string;}"
//!
//! Source Mapping:
//!   Segment 1: original[0-11] -> expanded[0-11]  (unchanged: "class Foo {")
//!   Generated: expanded[11-25] = " bar: string;" (from macro "Test")
//!   Segment 2: original[11-12] -> expanded[25-26] (unchanged: "}")
//! ```
//!
//! ## Position Conventions
//!
//! All [`SpanIR`] positions in patches use **1-based** byte offsets (matching SWC's
//! internal convention). The [`SourceMapping`] output uses **0-based** positions
//! (matching the TypeScript language service API). The applicator converts between
//! these conventions internally.
//!
//! ## Source Mapping
//!
//! The engine generates source mapping data that enables:
//! - Converting positions from original to expanded code
//! - Converting positions from expanded back to original
//! - Identifying which macro generated specific code regions
//! - Mapping IDE diagnostics to original source locations
//!
//! ## Example Usage
//!
//! ```rust,no_run
//! use macroforge_ts::host::patch_applicator::{PatchApplicator, PatchCollector};
//! use macroforge_ts::host::Result;
//! use macroforge_ts::ts_syn::abi::{Patch, SpanIR};
//!
//! fn example() -> Result<()> {
//!     let source = "class Foo {}";
//!
//!     // Using PatchApplicator directly
//!     let patch = Patch::Insert {
//!         at: SpanIR { start: 12, end: 12 },  // 1-based position
//!         code: " bar: string;".into(),
//!         source_macro: Some("Test".to_string()),
//!     };
//!
//!     let applicator = PatchApplicator::new(source, vec![patch]);
//!     let result = applicator.apply_with_mapping(None)?;
//!
//!     assert_eq!(result.code, "class Foo { bar: string;}");
//!     assert!(!result.mapping.is_empty());
//!
//!     // Using PatchCollector for multiple macros
//!     let mut collector = PatchCollector::new();
//!
//!     // Add patches from different macros
//!     let debug_patch = Patch::Insert {
//!         at: SpanIR { start: 12, end: 12 },
//!         code: " toString() { return 'Foo'; }".into(),
//!         source_macro: Some("Debug".to_string()),
//!     };
//!     collector.add_runtime_patches(vec![debug_patch]);
//!
//!     // Apply all collected patches
//!     let runtime_result = collector.apply_runtime_patches_with_mapping(source, None)?;
//!     Ok(())
//! }
//! ```

mod applicator;
mod collector;
mod helpers;
#[cfg(test)]
mod tests;

pub use applicator::{ApplyResult, PatchApplicator};
pub use collector::PatchCollector;