1#![forbid(unsafe_code)]
6
7#[cfg(test)]
8todo_or_die::crates_io!("bincode", ">=2");
9
10mod extract;
11use proc_macro_error::{abort, proc_macro_error};
12use quote::{format_ident, quote, quote_spanned};
13use syn::{parse_macro_input, parse_quote, spanned::Spanned};
14
15#[proc_macro]
16pub fn remote_addr(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
17 quote!(_turbocharger_connection_info.as_ref().and_then(|ref i| i.remote_addr)).into()
18}
19
20#[proc_macro]
21pub fn user_agent(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
22 quote!(_turbocharger_connection_info.as_ref().and_then(|ref i| i.user_agent.as_ref())).into()
23}
24
25struct ConnectionLocal {
26 pub ident: syn::Ident,
27 pub ty: syn::Type,
28}
29
30impl syn::parse::Parse for ConnectionLocal {
31 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
32 let ident = input.parse()?;
33
34 let _: syn::Token![:] = input.parse()?;
35 let _: syn::Token![&] = input.parse()?;
36 let _: syn::Token![mut] = input.parse()?;
37
38 let ty = input.parse()?;
39 Ok(Self { ident, ty })
40 }
41}
42
43#[proc_macro]
44pub fn connection_local(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
45 let ConnectionLocal { ident, ty } = parse_macro_input!(input as ConnectionLocal);
46 let ident_str = ident.to_string();
47
48 quote! {
49 let mut _turbocharger_connection_local_map = match _turbocharger_connection_info.as_ref() {
50 Some(c) => Some(c.connection_local.lock().await),
51 None => None,
52 };
53
54 let #ident = {
55 _turbocharger_connection_local_map
56 .as_mut()
57 .unwrap()
58 .entry(( #ident_str , std::any::TypeId::of::< #ty >()))
59 .or_insert_with(|| Box::new( #ty ::default()))
60 .downcast_mut::< #ty >()
61 .unwrap()
62 };
63 }
64 .into()
65}
66
67#[proc_macro_attribute]
71pub fn server_only(
72 _args: proc_macro::TokenStream,
73 input: proc_macro::TokenStream,
74) -> proc_macro::TokenStream {
75 let orig_item = parse_macro_input!(input as syn::Item);
76 proc_macro::TokenStream::from(quote! {
77 #[cfg(not(target_arch = "wasm32"))]
78 #orig_item
79 })
80}
81
82#[proc_macro_attribute]
86pub fn wasm_only(
87 _args: proc_macro::TokenStream,
88 input: proc_macro::TokenStream,
89) -> proc_macro::TokenStream {
90 let orig_item = parse_macro_input!(input as syn::Item);
91 proc_macro::TokenStream::from(quote! {
92 #[cfg(any(feature = "wasm", target_arch = "wasm32"))]
93 #[allow(unused_imports)]
94 use wasm_bindgen::prelude::*;
95
96 #[cfg(any(feature = "wasm", target_arch = "wasm32"))]
97 #orig_item
98 })
99}
100
101#[proc_macro_attribute]
103#[proc_macro_error]
104pub fn backend(
105 args: proc_macro::TokenStream,
106 input: proc_macro::TokenStream,
107) -> proc_macro::TokenStream {
108 backend_item(args, syn::parse_macro_input!(input as syn::Item)).into()
109}
110
111fn backend_item(args: proc_macro::TokenStream, orig_item: syn::Item) -> proc_macro2::TokenStream {
112 match orig_item {
113 syn::Item::Fn(orig) => backend_fn(args, orig),
114 syn::Item::Struct(orig) => backend_struct(orig),
115 _ => abort!(orig_item, "Apply #[backend] to `fn` or `struct`."),
117 }
118}
119
120fn backend_struct(orig_struct: syn::ItemStruct) -> proc_macro2::TokenStream {
142 if std::env::current_exe().unwrap().file_stem().unwrap() != "rust-analyzer" {
143 let mut api_struct = orig_struct.clone();
144 api_struct.vis = parse_quote!();
145 api_struct.attrs.retain(|attr| attr.path.is_ident("doc"));
146 for field in &mut api_struct.fields {
147 field.vis = parse_quote!();
148 field.attrs.retain(|attr| attr.path.is_ident("doc"));
149 }
150
151 let lockfile = std::fs::File::create(std::env::temp_dir().join("turbocharger.lock")).unwrap();
152 fs2::FileExt::lock_exclusive(&lockfile).unwrap();
153
154 let mut file = read_backend_api_rs();
156
157 let mut found = false;
158 for item in &mut file.items {
159 if let syn::Item::Struct(ref mut item) = item {
160 if item.ident == api_struct.ident {
161 found = true;
162 *item = api_struct.clone();
163 break;
164 }
165 }
166 }
167 if !found {
168 file.items.push(syn::Item::Struct(api_struct));
169 }
170
171 write_backend_api_rs(file);
172 }
173
174 #[allow(clippy::redundant_clone)]
175 let syn::ItemStruct { attrs, ident, fields, .. } = orig_struct.clone();
176
177 let output = quote! {
178 #[cfg(target_arch = "wasm32")]
179 #[allow(unused_imports)]
180 use wasm_bindgen::prelude::*;
181
182 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone, inspectable))]
183 #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize, Clone)]
184 #(#attrs)*
185 #[serde(crate = "::turbocharger::serde")]
186 pub struct #ident #fields
187
188 #[cfg(target_arch = "wasm32")]
189 #[wasm_bindgen]
190 impl #ident {
191 #[wasm_bindgen(constructor)]
192 pub fn new() -> #ident {
193 #ident::default()
194 }
195 }
196 };
197
198 #[cfg(feature = "debug_expansions")]
199 {
200 std::fs::create_dir_all("debug_expansions").ok();
201 std::fs::write(
202 project_root_path_with(format!("debug_expansions/{}.rs", orig_struct.ident)),
203 prettyplease::unparse(&parse_quote!( #output )),
204 )
205 .unwrap();
206
207 std::process::Command::new("rustfmt")
208 .current_dir(project_root_path_with("debug_expansions"))
209 .arg("--edition")
210 .arg("2021")
211 .arg("--config")
212 .arg("tab_spaces=1")
213 .arg(format!("{}.rs", orig_struct.ident))
214 .output()
215 .unwrap();
216 }
217
218 output
219}
220
221fn backend_fn(args: proc_macro::TokenStream, orig_fn: syn::ItemFn) -> proc_macro2::TokenStream {
222 let is_js = args.to_string() == "js";
223
224 if std::env::current_exe().unwrap().file_stem().unwrap() != "rust-analyzer" {
225 let mut api_fn = orig_fn.clone();
226 api_fn.vis = parse_quote!();
227 api_fn.attrs.retain(|attr| attr.path.is_ident("doc"));
228 api_fn.block = parse_quote!({});
229
230 let lockfile = std::fs::File::create(std::env::temp_dir().join("turbocharger.lock")).unwrap();
231 fs2::FileExt::lock_exclusive(&lockfile).unwrap();
232
233 let mut file = read_backend_api_rs();
235
236 let mut found = false;
237 for item in &mut file.items {
238 if let syn::Item::Fn(ref mut item) = item {
239 if item.sig.ident == api_fn.sig.ident {
240 found = true;
241 *item = api_fn.clone();
242 break;
243 }
244 }
245 }
246 if !found {
247 file.items.push(syn::Item::Fn(api_fn));
248 }
249
250 write_backend_api_rs(file);
251 }
252
253 let orig_fn_ident = orig_fn.sig.ident.clone();
254 let orig_fn_string = orig_fn_ident.to_string();
255 let orig_fn_params = orig_fn.sig.inputs.clone();
256 let orig_fn_stmts = &orig_fn.block.stmts;
257 let orig_fn_stmts = quote!(#( #orig_fn_stmts )*);
258
259 let store_name = format_ident!("_TURBOCHARGER_STORE_{}", orig_fn_ident);
260 let dispatch = format_ident!("_TURBOCHARGER_DISPATCH_{}", orig_fn_ident);
261 let req = format_ident!("_TURBOCHARGER_REQ_{}", orig_fn_ident);
262 let resp = format_ident!("_TURBOCHARGER_RESP_{}", orig_fn_ident);
263 let js_fn_ident = format_ident!("_TURBOCHARGER_JS_{}", orig_fn_ident);
264 let remote_fn_ident = format_ident!("remote_{}", orig_fn_ident);
265 let remote_impl_ident = format_ident!("_TURBOCHARGER_REMOTEIMPL_{}", orig_fn_ident);
266 let subscriber_fn_ident = format_ident!("_TURBOCHARGER_SUBSCRIBERFN_{}", orig_fn_ident);
267
268 let orig_fn_ret_ty = match orig_fn.sig.output.clone() {
269 syn::ReturnType::Type(_, path) => *path,
270 syn::ReturnType::Default => parse_quote! { () },
271 };
272 let stream_inner_ty = extract::extract_stream(&orig_fn_ret_ty);
273 let result_inner_ty = extract::extract_result(stream_inner_ty.unwrap_or(&orig_fn_ret_ty));
274 let store_value_ty = if result_inner_ty.is_some() {
275 quote! { Result<#result_inner_ty, JsValue> }
276 } else {
277 quote! { #stream_inner_ty }
278 };
279
280 let maybe_map_err_jsvalue = match result_inner_ty {
281 Some(_) => quote! { .map_err(|e| ::turbocharger::js_sys::Error::new(&e.to_string()).into()) },
282 None => quote! {},
283 };
284
285 let send_value_to_subscription = if result_inner_ty.is_some() {
286 quote! {
287 if let Some(value) = self.value.lock().unwrap().clone() {
288 let promise: ::turbocharger::js_sys::Promise = match value.clone() {
289 Ok(t) => ::turbocharger::js_sys::Promise::resolve(&t.into()).into(),
290 Err(e) => ::turbocharger::js_sys::Promise::reject(&e.into()).into(),
291 };
292 subscription.call1(&JsValue::null(), &promise).ok();
293 }
294 }
295 } else {
296 quote! {
297 if let Some(value) = self.value.lock().unwrap().clone() {
298 subscription.call1(&JsValue::null(), &value.into()).ok();
299 }
300 }
301 };
302
303 let send_value_to_subscriptions = if result_inner_ty.is_some() {
304 quote! {
305 if let Some(value) = value.lock().unwrap().clone() {
306 let promise: ::turbocharger::js_sys::Promise = match value.clone() {
307 Ok(t) => ::turbocharger::js_sys::Promise::resolve(&t.into()).into(),
308 Err(e) => ::turbocharger::js_sys::Promise::reject(&e.into()).into(),
309 };
310 for subscription in subscriptions.lock().unwrap().iter() {
311 if let Some(subscription) = subscription.lock().unwrap().as_ref() {
312 subscription.call1(&JsValue::null(), &promise).ok();
313 }
314 }
315 }
316 }
317 } else {
318 quote! {
319 if let Some(value) = value.lock().unwrap().clone() {
320 for subscription in subscriptions.lock().unwrap().iter() {
321 if let Some(subscription) = subscription.lock().unwrap().as_ref() {
322 subscription.call1(&JsValue::null(), &value.clone().into()).ok();
323 }
324 }
325 }
326 }
327 };
328
329 let bindgen_ret_ty = match (stream_inner_ty, result_inner_ty) {
330 (None, Some(ty)) => quote! { Result<#ty, JsValue> },
331 (Some(_ty), _) => quote! { #store_name },
332 (None, None) => quote! { #orig_fn_ret_ty },
333 };
334 let serialize_ret_ty = match &stream_inner_ty {
335 Some(ty) => quote! { #ty },
336 None => quote! { #orig_fn_ret_ty },
337 };
338
339 let orig_fn_ret_ty = if let Some(ty) = stream_inner_ty {
340 quote_spanned! {ty.span()=> impl ::turbocharger::futures_util::stream::Stream<Item = #ty > }
341 } else {
342 quote_spanned! {orig_fn_ret_ty.span()=> #orig_fn_ret_ty }
343 };
344
345 let tuple_indexes = (0..orig_fn_params.len()).map(syn::Index::from);
346 let orig_fn_param_names: Vec<_> = orig_fn_params
347 .iter()
348 .map(|p| match p {
349 syn::FnArg::Receiver(_) => abort!(p, "I don't know what to do with `self` here."),
350 syn::FnArg::Typed(pattype) => match *pattype.pat.clone() {
351 syn::Pat::Ident(i) => i.ident,
352 _ => abort!(pattype, "Parameter name is not Ident"),
353 },
354 })
355 .collect();
356
357 let orig_fn_param_tys: Vec<_> = orig_fn_params
358 .iter()
359 .map(|p| match p {
360 syn::FnArg::Receiver(_) => abort!(p, "I don't know what to do with `self` here."),
361 syn::FnArg::Typed(pattype) => &pattype.ty,
362 })
363 .collect();
364
365 let orig_fn_params_maybe_comma = if orig_fn_params.is_empty() { quote!() } else { quote!( , ) };
366
367 let mut orig_fn = orig_fn;
368 orig_fn.sig.output = parse_quote! { -> #orig_fn_ret_ty };
369 orig_fn.block = parse_quote!({
370 let _turbocharger_connection_info: Option<::turbocharger::ConnectionInfo> = None;
371 #orig_fn_stmts
372 });
373
374 let mut remote_impl_fn = orig_fn.clone();
375 remote_impl_fn.sig.ident = remote_impl_ident.clone();
376 remote_impl_fn.sig.inputs = parse_quote!(
377 _turbocharger_connection_info: Option<::turbocharger::ConnectionInfo>
378 #orig_fn_params_maybe_comma
379 #orig_fn_params
380 );
381 remote_impl_fn.block = parse_quote!({ #orig_fn_stmts });
382
383 let executebody = match &stream_inner_ty {
384 Some(_ty) => quote! {
385 use ::turbocharger::futures_util::stream::StreamExt as _;
386 use ::turbocharger::stream_cancel::StreamExt as _;
387 let stream = #remote_impl_ident(_turbocharger_connection_info #orig_fn_params_maybe_comma #( self.params. #tuple_indexes .clone() ),*);
388 ::turbocharger::futures_util::pin_mut!(stream);
389
390 if let Some(tripwire) = tripwire {
391 let mut incoming = stream.take_until_if(tripwire);
392 while let Some(result) = incoming.next().await {
393 let response = #resp {
394 txid: self.txid,
395 result: result.clone()
396 };
397 sender(::turbocharger::bincode::serialize(&response).unwrap());
398 }
399 }
400 else {
401 while let Some(result) = stream.next().await {
402 let response = #resp {
403 txid: self.txid,
404 result: result.clone()
405 };
406 sender(::turbocharger::bincode::serialize(&response).unwrap());
407 }
408 }
409 },
410 None => quote! {
411 let result = #remote_impl_ident(_turbocharger_connection_info #orig_fn_params_maybe_comma #( self.params. #tuple_indexes .clone() ),*).await;
412 let response = #resp {
413 txid: self.txid,
414 result
415 };
416 sender(::turbocharger::bincode::serialize(&response).unwrap());
417 },
418 };
419
420 let maybe_svelte_typescript_type = if cfg!(feature = "svelte") {
421 quote! {
422 #[cfg(target_arch = "wasm32")]
423 #[wasm_bindgen]
424 extern "C" {
425 #[wasm_bindgen(typescript_type = "Subscriber<any>")]
426 #[allow(non_camel_case_types)]
427 pub type #subscriber_fn_ident;
428 }
429 }
430 } else {
431 quote!()
432 };
433
434 let wasm_side = match &stream_inner_ty {
435 Some(_ty) => quote! {
436 #[cfg(target_arch = "wasm32")]
437 pub fn #orig_fn_ident(#orig_fn_params) -> #orig_fn_ret_ty {
438 let tx = ::turbocharger::_Transaction::new();
439 let req = ::turbocharger::bincode::serialize(&#req {
440 typetag_const_one: 1,
441 dispatch_name: #orig_fn_string,
442 txid: tx.txid,
443 params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
444 })
445 .unwrap();
446 tx.send_ws(req);
447
448 let (resp_tx, resp_rx) = ::turbocharger::futures_channel::mpsc::unbounded();
449
450 tx.set_sender(Box::new(move |response| {
451 use ::turbocharger::futures_util::SinkExt as _;
452 let #resp { result, .. } =
453 ::turbocharger::bincode::deserialize(&response).unwrap();
454 let mut resp_tx = resp_tx.clone();
455 wasm_bindgen_futures::spawn_local(async move {
456 resp_tx.send(result).await.unwrap();
457 });
458 }));
459
460 resp_rx
461 }
462 },
463 None => quote! {
464 #[cfg(target_arch = "wasm32")]
465 pub async fn #orig_fn_ident(#orig_fn_params) -> #orig_fn_ret_ty {
466 let tx = ::turbocharger::_Transaction::new();
467 let req = ::turbocharger::bincode::serialize(&#req {
468 typetag_const_one: 1,
469 dispatch_name: #orig_fn_string,
470 txid: tx.txid,
471 params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
472 })
473 .unwrap();
474 tx.send_ws(req);
475 let response = tx.resp().await;
476 let #resp { result, .. } =
477 ::turbocharger::bincode::deserialize(&response).unwrap();
478 result
479 }
480 },
481 };
482
483 let js_side = if !is_js {
484 quote!()
485 } else {
486 match &stream_inner_ty {
487 Some(_ty) => quote! {
488 #[cfg(target_arch = "wasm32")]
489 #[allow(non_camel_case_types)]
490 #[wasm_bindgen]
491 pub struct #store_name {
492 req: std::sync::Arc<std::sync::Mutex<#req>>,
493 value: std::sync::Arc<std::sync::Mutex<Option< #store_value_ty >>>,
494 subscriptions: std::sync::Arc<std::sync::Mutex<Vec<std::sync::Arc<std::sync::Mutex<Option<::turbocharger::js_sys::Function>>>>>>,
495 }
496
497 #maybe_svelte_typescript_type
498
499 #[cfg(target_arch = "wasm32")]
500 #[wasm_bindgen]
501 impl #store_name {
502 #[wasm_bindgen]
503 pub fn subscribe(&mut self, subscription: #subscriber_fn_ident) -> JsValue {
504 let subscription: ::turbocharger::js_sys::Function = JsValue::from(subscription).into();
505 if self.subscriptions.lock().unwrap().is_empty() {
506 let tx = ::turbocharger::_Transaction::new();
507 self.req.lock().unwrap().txid = tx.txid;
508 tx.send_ws(::turbocharger::bincode::serialize(&*self.req.lock().unwrap()).unwrap());
509 let subscriptions = self.subscriptions.clone();
510 let value = self.value.clone();
511 tx.set_sender(Box::new(move |response| {
512 let #resp { result, .. } =
513 ::turbocharger::bincode::deserialize(&response).unwrap();
514 value.lock().unwrap().replace(result.clone() #maybe_map_err_jsvalue );
515 #send_value_to_subscriptions
516 }));
517 }
518
519 #send_value_to_subscription
520 let subscription_handle = std::sync::Arc::new(std::sync::Mutex::new(Some(subscription)));
521 self.subscriptions.lock().unwrap().push(subscription_handle.clone());
522 let subscriptions = self.subscriptions.clone();
523 let req_clone = self.req.clone();
524
525 Closure::wrap(Box::new(move || {
526 subscription_handle.lock().unwrap().take();
527 subscriptions.lock().unwrap().retain(|s| { s.lock().unwrap().is_some() });
528 if subscriptions.lock().unwrap().is_empty() {
529 let tx = ::turbocharger::_Transaction::new();
530 tx.send_ws(::turbocharger::bincode::serialize(&*req_clone.lock().unwrap()).unwrap());
531 }
532 }) as Box<dyn Fn()>)
533 .into_js_value()
534 }
535 }
536
537 #[cfg(target_arch = "wasm32")]
538 #[allow(non_snake_case)]
539 #[wasm_bindgen(js_name = #orig_fn_ident)]
540 pub fn #js_fn_ident(#orig_fn_params) -> #bindgen_ret_ty {
541 let req = #req {
542 typetag_const_one: 1,
543 dispatch_name: #orig_fn_string,
544 txid: 1,
545 params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
546 };
547 #store_name {
548 req: std::sync::Arc::new(std::sync::Mutex::new(req)),
549 value: Default::default(),
550 subscriptions: Default::default()
551 }
552 }
553 },
554 None => quote! {
555 #[cfg(target_arch = "wasm32")]
556 #[allow(non_snake_case)]
557 #[wasm_bindgen(js_name = #orig_fn_ident)]
558 pub async fn #js_fn_ident(#orig_fn_params) -> #bindgen_ret_ty {
559 #orig_fn_ident(#( #orig_fn_param_names ),*) .await #maybe_map_err_jsvalue
560 }
561 },
562 }
563 };
564
565 let output = quote! {
566 #[cfg(target_arch = "wasm32")]
567 #[allow(unused_imports)]
568 use wasm_bindgen::prelude::*;
569
570 #[cfg(not(target_arch = "wasm32"))]
571 #[allow(unused_imports)]
572 use turbocharger::prelude::*;
573
574 #[cfg(not(target_arch = "wasm32"))]
575 #[tracked]
576 #orig_fn
577
578 #[cfg(not(target_arch = "wasm32"))]
579 #[allow(non_snake_case)]
580 #[tracked]
581 #remote_impl_fn
582
583 #[cfg(not(target_arch = "wasm32"))]
584 #[allow(non_snake_case)]
585 #[::turbocharger::typetag::serde(name = #orig_fn_string)]
586 #[::turbocharger::async_trait]
587 impl ::turbocharger::RPC for #dispatch {
588 async fn execute(
589 &self,
590 sender: Box<dyn Fn(Vec<u8>) + Send>,
591 tripwire: Option<::turbocharger::stream_cancel::Tripwire>,
592 _turbocharger_connection_info: Option<::turbocharger::ConnectionInfo>
593 ) {
594 #executebody
595 }
596 fn txid(&self) -> i64 {
597 self.txid
598 }
599 }
600
601 #wasm_side
602 #js_side
603
604 #[cfg(not(target_arch = "wasm32"))]
605 #[allow(non_snake_case)]
606 async fn #remote_fn_ident(peer: &str, #orig_fn_params) -> #serialize_ret_ty {
607 let tx = ::turbocharger::_Transaction::new();
608 let req = ::turbocharger::bincode::serialize(&#req {
609 typetag_const_one: 1,
610 dispatch_name: #orig_fn_string,
611 txid: tx.txid,
612 params: (#( #orig_fn_param_names ),* #orig_fn_params_maybe_comma),
613 })
614 .unwrap();
615 tx.send_udp(peer, req).await;
616 let response = tx.resp().await;
617 let #resp { result, .. } =
618 ::turbocharger::bincode::deserialize(&response).unwrap();
619 result
620 }
621
622 #[allow(non_camel_case_types)]
623 #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize)]
624 #[serde(crate = "::turbocharger::serde")]
625 struct #req {
626 typetag_const_one: i64,
627 dispatch_name: &'static str,
628 txid: i64,
629 params: (#( #orig_fn_param_tys ),* #orig_fn_params_maybe_comma),
630 }
631
632 #[allow(non_camel_case_types)]
633 #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize)]
634 #[serde(crate = "::turbocharger::serde")]
635 struct #dispatch {
636 txid: i64,
637 params: (#( #orig_fn_param_tys ),* #orig_fn_params_maybe_comma),
638 }
639
640 #[allow(non_camel_case_types)]
641 #[derive(::turbocharger::serde::Serialize, ::turbocharger::serde::Deserialize)]
642 #[serde(crate = "::turbocharger::serde")]
643 struct #resp {
644 txid: i64,
645 result: #serialize_ret_ty,
646 }
647 };
648
649 #[cfg(feature = "debug_expansions")]
650 {
651 std::fs::create_dir_all("debug_expansions").ok();
652 std::fs::write(
653 project_root_path_with(format!("debug_expansions/{}.rs", orig_fn_string)),
654 prettyplease::unparse(&parse_quote!( #output )),
655 )
656 .unwrap();
657
658 std::process::Command::new("rustfmt")
659 .current_dir(project_root_path_with("debug_expansions"))
660 .arg("--edition")
661 .arg("2021")
662 .arg("--config")
663 .arg("tab_spaces=1")
664 .arg(format!("{}.rs", orig_fn_string))
665 .output()
666 .unwrap();
667 }
668
669 output
670}
671
672fn read_backend_api_rs() -> syn::File {
673 syn::parse_file(&std::fs::read_to_string(backend_api_rs_path()).unwrap_or_default()).unwrap()
674}
675
676fn write_backend_api_rs(file: syn::File) {
677 let mut output = "// This file is auto-generated by Turbocharger.\n// Check it into version control to track API changes over time.\n// To regenerate: \"cargo clean && rm backend_api.rs && cargo check\"\n".to_string();
678 let mut items = file.items;
679 items.sort_by_key(|item| match item {
680 syn::Item::Struct(s) => s.ident.to_string().to_lowercase(),
681 syn::Item::Fn(f) => f.sig.ident.to_string().to_lowercase(),
682 _ => unreachable!(),
683 });
684 for item in items {
685 output.push('\n');
686 output.push_str(&prettyplease::unparse(&parse_quote!( #item )));
687 }
688 if output != std::fs::read_to_string(backend_api_rs_path()).unwrap_or_default() {
689 std::fs::write(backend_api_rs_path(), output).unwrap();
690 }
691}
692
693fn backend_api_rs_path() -> std::path::PathBuf {
694 project_root_path_with("backend_api.rs")
695}
696
697fn project_root_path_with<P: AsRef<std::path::Path>>(pushpath: P) -> std::path::PathBuf {
698 let mut path = std::path::PathBuf::from(env!("OUT_DIR"));
699 while path.file_name() != Some(std::ffi::OsStr::new("target")) {
700 path.pop();
701 }
702 path.pop();
703 path.push(pushpath);
704 path
705}