diff options
Diffstat (limited to 'cli/swc_util.rs')
-rw-r--r-- | cli/swc_util.rs | 247 |
1 files changed, 194 insertions, 53 deletions
diff --git a/cli/swc_util.rs b/cli/swc_util.rs index 7c7d01832..1a058d6da 100644 --- a/cli/swc_util.rs +++ b/cli/swc_util.rs @@ -29,6 +29,10 @@ use swc_ecmascript::parser::StringInput; use swc_ecmascript::parser::Syntax; use swc_ecmascript::parser::TsConfig; use swc_ecmascript::transforms::fixer; +use swc_ecmascript::transforms::helpers; +use swc_ecmascript::transforms::pass::Optional; +use swc_ecmascript::transforms::proposals::decorators; +use swc_ecmascript::transforms::react; use swc_ecmascript::transforms::typescript; use swc_ecmascript::visit::FoldWith; @@ -230,52 +234,6 @@ impl AstParser { }) } - pub fn strip_types( - &self, - file_name: &str, - media_type: MediaType, - source_code: &str, - ) -> Result<String, ErrBox> { - let module = self.parse_module(file_name, media_type, source_code)?; - let program = Program::Module(module); - let mut compiler_pass = - chain!(typescript::strip(), fixer(Some(&self.comments))); - let program = program.fold_with(&mut compiler_pass); - - let mut src_map_buf = vec![]; - let mut buf = vec![]; - { - let writer = Box::new(JsWriter::new( - self.source_map.clone(), - "\n", - &mut buf, - Some(&mut src_map_buf), - )); - let config = swc_ecmascript::codegen::Config { minify: false }; - let mut emitter = swc_ecmascript::codegen::Emitter { - cfg: config, - comments: Some(&self.comments), - cm: self.source_map.clone(), - wr: writer, - }; - program.emit_with(&mut emitter)?; - } - let mut src = String::from_utf8(buf)?; - { - let mut buf = vec![]; - self - .source_map - .build_source_map_from(&mut src_map_buf, None) - .to_writer(&mut buf)?; - let map = String::from_utf8(buf)?; - - src.push_str("//# sourceMappingURL=data:application/json;base64,"); - let encoded_map = base64::encode(map.as_bytes()); - src.push_str(&encoded_map); - } - Ok(src) - } - pub fn get_span_location(&self, span: Span) -> swc_common::Loc { self.source_map.lookup_char_pos(span.lo()) } @@ -290,13 +248,196 @@ impl AstParser { } } -#[test] -fn test_strip_types() { +#[derive(Debug, Clone)] +pub struct EmitTranspileOptions { + /// When emitting a legacy decorator, also emit experimental decorator meta + /// data. Defaults to `false`. + pub emit_metadata: bool, + /// Should the source map be inlined in the emitted code file, or provided + /// as a separate file. Defaults to `true`. + pub inline_source_map: bool, + /// When transforming JSX, what value should be used for the JSX factory. + /// Defaults to `React.createElement`. + pub jsx_factory: String, + /// When transforming JSX, what value should be used for the JSX fragment + /// factory. Defaults to `React.Fragment`. + pub jsx_fragment_factory: String, + /// Should JSX be transformed or preserved. Defaults to `true`. + pub transform_jsx: bool, +} + +impl Default for EmitTranspileOptions { + fn default() -> Self { + EmitTranspileOptions { + emit_metadata: false, + inline_source_map: true, + jsx_factory: "React.createElement".into(), + jsx_fragment_factory: "React.Fragment".into(), + transform_jsx: true, + } + } +} + +pub fn transpile( + file_name: &str, + media_type: MediaType, + source_code: &str, + options: &EmitTranspileOptions, +) -> Result<(String, Option<String>), ErrBox> { let ast_parser = AstParser::default(); - let result = ast_parser - .strip_types("test.ts", MediaType::TypeScript, "const a: number = 10;") + let module = ast_parser.parse_module(file_name, media_type, source_code)?; + let program = Program::Module(module); + + let jsx_pass = react::react( + ast_parser.source_map.clone(), + react::Options { + pragma: options.jsx_factory.clone(), + pragma_frag: options.jsx_fragment_factory.clone(), + // this will use `Object.assign()` instead of the `_extends` helper + // when spreading props. + use_builtins: true, + ..Default::default() + }, + ); + let mut passes = chain!( + Optional::new(jsx_pass, options.transform_jsx), + decorators::decorators(decorators::Config { + legacy: true, + emit_metadata: options.emit_metadata, + }), + typescript::strip(), + fixer(Some(&ast_parser.comments)), + ); + + let program = swc_common::GLOBALS.set(&Globals::new(), || { + helpers::HELPERS.set(&helpers::Helpers::new(false), || { + program.fold_with(&mut passes) + }) + }); + + let mut src_map_buf = vec![]; + let mut buf = vec![]; + { + let writer = Box::new(JsWriter::new( + ast_parser.source_map.clone(), + "\n", + &mut buf, + Some(&mut src_map_buf), + )); + let config = swc_ecmascript::codegen::Config { minify: false }; + let mut emitter = swc_ecmascript::codegen::Emitter { + cfg: config, + comments: Some(&ast_parser.comments), + cm: ast_parser.source_map.clone(), + wr: writer, + }; + program.emit_with(&mut emitter)?; + } + let mut src = String::from_utf8(buf)?; + let mut map: Option<String> = None; + { + let mut buf = Vec::new(); + ast_parser + .source_map + .build_source_map_from(&mut src_map_buf, None) + .to_writer(&mut buf)?; + + if options.inline_source_map { + src.push_str("//# sourceMappingURL=data:application/json;base64,"); + let encoded_map = base64::encode(buf); + src.push_str(&encoded_map); + } else { + map = Some(String::from_utf8(buf)?); + } + } + Ok((src, map)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transpile() { + let source = r#" + enum D { + A, + B, + C, + } + export class A { + private b: string; + protected c: number = 1; + e: "foo"; + constructor (public d = D.A) { + const e = "foo" as const; + this.e = e; + } + } + "#; + let result = transpile( + "test.ts", + MediaType::TypeScript, + source, + &EmitTranspileOptions::default(), + ) .unwrap(); - assert!(result.starts_with( - "const a = 10;\n//# sourceMappingURL=data:application/json;base64," - )); + let (code, maybe_map) = result; + assert!(code.starts_with("var D;\n(function(D) {\n")); + assert!( + code.contains("\n//# sourceMappingURL=data:application/json;base64,") + ); + assert!(maybe_map.is_none()); + } + + #[test] + fn test_transpile_tsx() { + let source = r#" + export class A { + render() { + return <div><span></span></div> + } + } + "#; + let result = transpile( + "test.ts", + MediaType::TSX, + source, + &EmitTranspileOptions::default(), + ) + .unwrap(); + let (code, _maybe_source_map) = result; + assert!(code.contains("React.createElement(\"div\", null")); + } + + #[test] + fn test_transpile_decorators() { + let source = r#" + function enumerable(value: boolean) { + return function ( + _target: any, + _propertyKey: string, + descriptor: PropertyDescriptor, + ) { + descriptor.enumerable = value; + }; + } + + export class A { + @enumerable(false) + a() { + Test.value; + } + } + "#; + let result = transpile( + "test.ts", + MediaType::TypeScript, + source, + &EmitTranspileOptions::default(), + ) + .unwrap(); + let (code, _maybe_source_map) = result; + assert!(code.contains("_applyDecoratedDescriptor(")); + } } |