mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
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".
This commit is contained in:
parent
7f32a4e19b
commit
b21f318e68
4 changed files with 278 additions and 58 deletions
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -16,6 +16,15 @@ version = "0.2.3"
|
|||
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"
|
||||
|
@ -265,6 +274,26 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -330,6 +359,17 @@ dependencies = [
|
|||
"syn 1.0.36",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -2262,6 +2302,7 @@ checksum = "c3e99fc5c5b87871fa19036fd8a622ecf6b2a29b30b4d632e48f6a1923e393ea"
|
|||
dependencies = [
|
||||
"Inflector",
|
||||
"arrayvec",
|
||||
"dashmap",
|
||||
"either",
|
||||
"fxhash",
|
||||
"indexmap",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
251
cli/swc_util.rs
251
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() {
|
||||
let ast_parser = AstParser::default();
|
||||
let result = ast_parser
|
||||
.strip_types("test.ts", MediaType::TypeScript, "const a: number = 10;")
|
||||
.unwrap();
|
||||
assert!(result.starts_with(
|
||||
"const a = 10;\n//# sourceMappingURL=data:application/json;base64,"
|
||||
));
|
||||
#[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 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();
|
||||
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("));
|
||||
}
|
||||
}
|
||||
|
|
42
cli/tsc.rs
42
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
|
||||
|
|
Loading…
Reference in a new issue