Skip to content
File

Blob: src/rust/transpiler/lib.rs

rust165 lines
1use std::cell::RefCell;
2use std::rc::Rc;
3 
4use swc_common::SourceMap;
5use swc_common::errors::DiagnosticBuilder;
6use swc_common::errors::Emitter;
7use swc_common::errors::HANDLER;
8use swc_common::errors::Handler;
9use swc_common::errors::Level;
10 
11use crate::ffi::Output;
12 
13#[cxx::bridge(namespace = "workerd::rust::transpiler")]
14mod ffi {
15 #[derive(Debug)]
16 struct Output {
17 success: bool,
18 // empty when error
19 code: String,
20 error: String,
21 diagnostics: Vec<Message>,
22 }
23 
24 #[derive(Debug, Clone, PartialEq, Eq)]
25 enum Level {
26 Error,
27 Warning,
28 }
29 
30 #[derive(Debug, Clone, PartialEq, Eq)]
31 struct Message {
32 level: Level,
33 message: String,
34 }
35 extern "Rust" {
36 
37 /// Strip typescript types from the source code.
38 /// Other typescript constructs line enum will result in error `Output`.
39 fn ts_strip(filename: &str, src: &[u8]) -> Output;
40 }
41}
42 
43fn ts_strip(filename: &str, src: &[u8]) -> Output {
44 tr_strip_string(filename, String::from_utf8_lossy(src).to_string())
45}
46 
47fn tr_strip_string(filename: &str, src: String) -> Output {
48 let cm: Rc<SourceMap> = Rc::default();
49 let errors = Box::new(MessagesCollector::default());
50 let messages = errors.messages.clone();
51 let handler = Handler::with_emitter(false, false, errors);
52 
53 let output = HANDLER.set(&handler, || {
54 swc_ts_fast_strip::operate(
55 &cm,
56 &handler,
57 src,
58 swc_ts_fast_strip::Options {
59 filename: Some(filename.to_owned()),
60 mode: swc_ts_fast_strip::Mode::StripOnly,
61 ..Default::default()
62 },
63 )
64 });
65 
66 match output {
67 Ok(output) => Output {
68 success: true,
69 code: output.code,
70 error: String::new(),
71 diagnostics: messages.borrow().clone(),
72 },
73 Err(err) => Output {
74 success: false,
75 code: String::new(),
76 error: err.message,
77 diagnostics: messages.borrow().clone(),
78 },
79 }
80}
81 
82/// Collects all swc emitted error message.
83#[derive(Default)]
84struct MessagesCollector {
85 messages: Rc<RefCell<Vec<ffi::Message>>>,
86}
87 
88impl Emitter for MessagesCollector {
89 fn emit(&mut self, db: &mut DiagnosticBuilder<'_>) {
90 if db.is_error() {
91 self.messages.borrow_mut().push(ffi::Message {
92 level: ffi::Level::Error,
93 message: db.message(),
94 });
95 } else if db.level == Level::Warning {
96 self.messages.borrow_mut().push(ffi::Message {
97 level: ffi::Level::Warning,
98 message: db.message(),
99 });
100 }
101 }
102}
103 
104#[cfg(test)]
105mod tests {
106 use crate::ffi::Output;
107 use crate::ffi::{self};
108 use crate::tr_strip_string;
109 
110 fn tr(src: &str) -> Output {
111 tr_strip_string("foo.ts", src.to_owned())
112 }
113 
114 #[test]
115 fn js() {
116 let out = tr("let x = 42;");
117 assert!(out.success);
118 assert_eq!("let x = 42;", out.code);
119 assert!(out.diagnostics.is_empty());
120 }
121 
122 #[test]
123 fn ts() {
124 let out = tr("let x: Number = 42;");
125 assert!(out.success);
126 assert_eq!("let x = 42;", out.code);
127 assert!(out.diagnostics.is_empty());
128 }
129 
130 #[test]
131 fn worker() {
132 assert_eq!(
133 r"
134export default {
135 async fetch(request, env, ctx) {
136 return new Response('Hello World from Typescript!');
137 },
138} ;",
139 tr(r"
140export default {
141 async fetch(request, env, ctx): Promise<Response> {
142 return new Response('Hello World from Typescript!');
143 },
144} satisfies ExportedHandler<Env>;")
145 .code
146 );
147 }
148 
149 #[test]
150 fn erase_enum() {
151 // only types are stripped, unsupported typescript construct are reported as errors
152 let out = tr(r"enum Foo { A,B,C }");
153 assert!(!out.success);
154 assert_eq!("", out.code);
155 assert_eq!("Unsupported syntax", out.error);
156 assert_eq!(
157 vec![ffi::Message {
158 level: ffi::Level::Error,
159 message: "TypeScript enum is not supported in strip-only mode".to_owned()
160 }],
161 out.diagnostics
162 );
163 }
164}