fresh_plugin_runtime/
ts_export.rs1use oxc_allocator::Allocator;
12use oxc_codegen::Codegen;
13use oxc_parser::Parser;
14use oxc_span::SourceType;
15use ts_rs::TS;
16
17use fresh_core::api::{
18 ActionPopupAction, ActionSpec, BackgroundProcessResult, BufferInfo, BufferSavedDiff,
19 CompositeHunk, CompositeLayoutConfig, CompositePaneStyle, CompositeSourceConfig,
20 CreateVirtualBufferInExistingSplitOptions, CreateVirtualBufferInSplitOptions,
21 CreateVirtualBufferOptions, CursorInfo, JsTextPropertyEntry, LayoutHints, SpawnResult,
22 TextPropertiesAtCursor, TsHighlightSpan, ViewTokenStyle, ViewTokenWire, ViewTokenWireKind,
23 ViewportInfo,
24};
25
26fn get_type_decl(type_name: &str) -> Option<String> {
31 match type_name {
34 "BufferInfo" => Some(BufferInfo::decl()),
36 "CursorInfo" => Some(CursorInfo::decl()),
37 "ViewportInfo" => Some(ViewportInfo::decl()),
38 "ActionSpec" => Some(ActionSpec::decl()),
39 "BufferSavedDiff" => Some(BufferSavedDiff::decl()),
40 "LayoutHints" => Some(LayoutHints::decl()),
41
42 "SpawnResult" => Some(SpawnResult::decl()),
44 "BackgroundProcessResult" => Some(BackgroundProcessResult::decl()),
45
46 "TsCompositeLayoutConfig" | "CompositeLayoutConfig" => Some(CompositeLayoutConfig::decl()),
48 "TsCompositeSourceConfig" | "CompositeSourceConfig" => Some(CompositeSourceConfig::decl()),
49 "TsCompositePaneStyle" | "CompositePaneStyle" => Some(CompositePaneStyle::decl()),
50 "TsCompositeHunk" | "CompositeHunk" => Some(CompositeHunk::decl()),
51
52 "ViewTokenWireKind" => Some(ViewTokenWireKind::decl()),
54 "ViewTokenStyle" => Some(ViewTokenStyle::decl()),
55 "ViewTokenWire" => Some(ViewTokenWire::decl()),
56
57 "TsActionPopupAction" | "ActionPopupAction" => Some(ActionPopupAction::decl()),
59 "TsHighlightSpan" => Some(TsHighlightSpan::decl()),
60
61 "TextPropertyEntry" | "JsTextPropertyEntry" => Some(JsTextPropertyEntry::decl()),
63 "CreateVirtualBufferOptions" => Some(CreateVirtualBufferOptions::decl()),
64 "CreateVirtualBufferInSplitOptions" => Some(CreateVirtualBufferInSplitOptions::decl()),
65 "CreateVirtualBufferInExistingSplitOptions" => {
66 Some(CreateVirtualBufferInExistingSplitOptions::decl())
67 }
68
69 "TextPropertiesAtCursor" => Some(TextPropertiesAtCursor::decl()),
71
72 _ => None,
73 }
74}
75
76const DEPENDENCY_TYPES: &[&str] = &[
80 "TextPropertyEntry", ];
82
83pub fn collect_ts_types() -> String {
88 use crate::backend::quickjs_backend::JSEDITORAPI_REFERENCED_TYPES;
89
90 let mut types = Vec::new();
91 let mut included = std::collections::HashSet::new();
92
93 for type_name in DEPENDENCY_TYPES {
95 if let Some(decl) = get_type_decl(type_name) {
96 types.push(decl);
97 included.insert(*type_name);
98 }
99 }
100
101 for type_name in JSEDITORAPI_REFERENCED_TYPES {
103 if included.contains(*type_name) {
104 continue;
105 }
106 if let Some(decl) = get_type_decl(type_name) {
107 types.push(decl);
108 included.insert(*type_name);
109 } else {
110 eprintln!(
112 "Warning: Type '{}' is referenced in API but not registered in get_type_decl()",
113 type_name
114 );
115 }
116 }
117
118 types.join("\n\n")
119}
120
121pub fn validate_typescript(source: &str) -> Result<(), String> {
125 let allocator = Allocator::default();
126 let source_type = SourceType::d_ts();
127
128 let parser_ret = Parser::new(&allocator, source, source_type).parse();
129
130 if parser_ret.errors.is_empty() {
131 Ok(())
132 } else {
133 let errors: Vec<String> = parser_ret
134 .errors
135 .iter()
136 .map(|e: &oxc_diagnostics::OxcDiagnostic| e.to_string())
137 .collect();
138 Err(format!("TypeScript parse errors:\n{}", errors.join("\n")))
139 }
140}
141
142pub fn format_typescript(source: &str) -> String {
147 let allocator = Allocator::default();
148 let source_type = SourceType::d_ts();
149
150 let parser_ret = Parser::new(&allocator, source, source_type).parse();
151
152 if !parser_ret.errors.is_empty() {
153 return source.to_string();
155 }
156
157 Codegen::new().build(&parser_ret.program).code
159}
160
161pub fn write_fresh_dts() -> Result<(), String> {
166 use crate::backend::quickjs_backend::{JSEDITORAPI_TS_EDITOR_API, JSEDITORAPI_TS_PREAMBLE};
167
168 let ts_types = collect_ts_types();
169
170 let content = format!(
171 "{}\n{}\n{}",
172 JSEDITORAPI_TS_PREAMBLE, ts_types, JSEDITORAPI_TS_EDITOR_API
173 );
174
175 validate_typescript(&content)?;
177
178 let formatted = format_typescript(&content);
180
181 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
183 let output_path = std::path::Path::new(&manifest_dir)
184 .join("plugins")
185 .join("lib")
186 .join("fresh.d.ts");
187
188 let should_write = match std::fs::read_to_string(&output_path) {
190 Ok(existing) => existing != formatted,
191 Err(_) => true,
192 };
193
194 if should_write {
195 if let Some(parent) = output_path.parent() {
196 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
197 }
198 std::fs::write(&output_path, &formatted).map_err(|e| e.to_string())?;
199 }
200
201 Ok(())
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
211 #[ignore]
212 fn write_fresh_dts_file() {
213 write_fresh_dts().expect("Failed to write fresh.d.ts");
215 println!("Successfully generated, validated, and formatted fresh.d.ts");
216 }
217}