summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-09-02 11:12:18 +0200
committerGitHub <noreply@github.com>2020-09-02 11:12:18 +0200
commitb21f318e6818ebbe73ac1aadd8fa03f85207a8a0 (patch)
tree6b84a8c07cf1c9039a2d05f6f7d2e6fcd1494ff4
parent7f32a4e19bfb2dbb752ce4b35775cf5bad455a6e (diff)
fix: support missing features in --no-check (#7289)
This commit adds "EmitTranspileOptions" to "transpile()" function, that allows to configure transpilation process based on the currently loaded "tsconfig.json".
-rw-r--r--Cargo.lock41
-rw-r--r--cli/Cargo.toml2
-rw-r--r--cli/swc_util.rs247
-rw-r--r--cli/tsc.rs42
4 files changed, 276 insertions, 56 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4253fc51d..1dce0138f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
+name = "ahash"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
+dependencies = [
+ "const-random",
+]
+
+[[package]]
name = "aho-corasick"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -266,6 +275,26 @@ dependencies = [
]
[[package]]
+name = "const-random"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a"
+dependencies = [
+ "const-random-macro",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a"
+dependencies = [
+ "getrandom",
+ "proc-macro-hack",
+]
+
+[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -331,6 +360,17 @@ dependencies = [
]
[[package]]
+name = "dashmap"
+version = "3.11.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f260e2fc850179ef410018660006951c1b55b79e8087e87111a2c388994b9b5"
+dependencies = [
+ "ahash",
+ "cfg-if",
+ "num_cpus",
+]
+
+[[package]]
name = "deno"
version = "1.3.2"
dependencies = [
@@ -2262,6 +2302,7 @@ checksum = "c3e99fc5c5b87871fa19036fd8a622ecf6b2a29b30b4d632e48f6a1923e393ea"
dependencies = [
"Inflector",
"arrayvec",
+ "dashmap",
"either",
"fxhash",
"indexmap",
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 3ecb5ca23..a8d1e6a99 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -62,7 +62,7 @@ serde_json = { version = "1.0.57", features = [ "preserve_order" ] }
sys-info = "0.7.0"
sourcemap = "6.0.1"
swc_common = { version = "=0.10.2", features = ["sourcemap"] }
-swc_ecmascript = { version = "=0.6.3", features = ["codegen", "parser", "transforms", "visit"] }
+swc_ecmascript = { version = "=0.6.3", features = ["codegen", "parser", "react", "transforms", "visit"] }
tempfile = "3.1.0"
termcolor = "1.1.0"
tokio = { version = "0.2.22", features = ["full"] }
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("));
+ }
}
diff --git a/cli/tsc.rs b/cli/tsc.rs
index cc902d196..799673694 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -18,6 +18,7 @@ use crate::permissions::Permissions;
use crate::source_maps::SourceMapGetter;
use crate::startup_data;
use crate::state::State;
+use crate::swc_util;
use crate::swc_util::AstParser;
use crate::swc_util::Location;
use crate::swc_util::SwcDiagnosticBuffer;
@@ -418,6 +419,16 @@ struct CompileResponse {
stats: Option<Vec<Stat>>,
}
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct TranspileTsOptions {
+ check_js: bool,
+ emit_decorator_metadata: bool,
+ jsx: String,
+ jsx_factory: String,
+ jsx_fragment_factory: String,
+}
+
// TODO(bartlomieju): possible deduplicate once TS refactor is stabilized
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -795,12 +806,39 @@ impl TsCompiler {
let mut emit_map = HashMap::new();
+ let mut compiler_options = json!({
+ "checkJs": false,
+ "emitDecoratorMetadata": false,
+ "jsx": "react",
+ "jsxFactory": "React.createElement",
+ "jsxFragmentFactory": "React.Fragment",
+ });
+
+ let compiler_config = self.config.clone();
+
+ tsc_config::json_merge(&mut compiler_options, &compiler_config.options);
+
+ warn_ignored_options(
+ compiler_config.maybe_ignored_options,
+ compiler_config.path.as_ref().unwrap(),
+ );
+
+ let compiler_options: TranspileTsOptions =
+ serde_json::from_value(compiler_options)?;
+
+ let transpile_options = swc_util::EmitTranspileOptions {
+ emit_metadata: compiler_options.emit_decorator_metadata,
+ inline_source_map: true,
+ jsx_factory: compiler_options.jsx_factory,
+ jsx_fragment_factory: compiler_options.jsx_fragment_factory,
+ transform_jsx: compiler_options.jsx == "react",
+ };
for source_file in source_files {
- let parser = AstParser::default();
- let stripped_source = parser.strip_types(
+ let (stripped_source, _maybe_source_map) = swc_util::transpile(
&source_file.file_name,
MediaType::TypeScript,
&source_file.source_code,
+ &transpile_options,
)?;
// TODO(bartlomieju): this is superfluous, just to make caching function happy