mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor: Compiler config in Rust (#7228)
* port tsc_config.rs * cleanup options * bring back allowNonTsExtension * try * fix test * fix test2 * move config for bundling * remove Transpile compile request * remove dead code * remove more dead code * remove checkJs regex * fix * handle config str for runtime APIs * lint * runtimeCompile config in Rust * runtimeCompile and runtimeTranspile config in Rust * fix * remove lint supression * upgrade: jsonc-parser 0.13.0 * remove unneeded to_string() * upgrade: jsonc-parser 0.14.0 * remove AsRef<str>
This commit is contained in:
parent
71f0171ab0
commit
c82c3b982e
6 changed files with 653 additions and 511 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -353,6 +353,7 @@ dependencies = [
|
|||
"http",
|
||||
"idna",
|
||||
"indexmap",
|
||||
"jsonc-parser",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log 0.4.11",
|
||||
|
@ -1024,6 +1025,12 @@ dependencies = [
|
|||
"swc_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonc-parser"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce9b3e88481b91c43f37e742879a70dd5855e59f736bf3cac9b1383d68c1186"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
|
|
|
@ -45,6 +45,7 @@ futures = "0.3.5"
|
|||
http = "0.2.1"
|
||||
idna = "0.2.0"
|
||||
indexmap = "1.5.1"
|
||||
jsonc-parser = "0.14.0"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
|
|
|
@ -63,6 +63,7 @@ mod test_runner;
|
|||
mod text_encoding;
|
||||
mod tokio_util;
|
||||
mod tsc;
|
||||
mod tsc_config;
|
||||
mod upgrade;
|
||||
pub mod version;
|
||||
mod web_worker;
|
||||
|
|
437
cli/tsc.rs
437
cli/tsc.rs
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::colors;
|
||||
use crate::diagnostics::Diagnostic;
|
||||
use crate::diagnostics::DiagnosticItem;
|
||||
|
@ -20,6 +21,7 @@ use crate::state::State;
|
|||
use crate::swc_util::AstParser;
|
||||
use crate::swc_util::Location;
|
||||
use crate::swc_util::SwcDiagnosticBuffer;
|
||||
use crate::tsc_config;
|
||||
use crate::version;
|
||||
use crate::worker::Worker;
|
||||
use core::task::Context;
|
||||
|
@ -43,6 +45,7 @@ use std::fs;
|
|||
use std::io;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
@ -182,9 +185,6 @@ impl Future for CompilerWorker {
|
|||
}
|
||||
|
||||
lazy_static! {
|
||||
// TODO(bartlomieju): use JSONC parser from dprint instead of Regex
|
||||
static ref CHECK_JS_RE: Regex =
|
||||
Regex::new(r#""checkJs"\s*?:\s*?true"#).unwrap();
|
||||
static ref DENO_TYPES_RE: Regex =
|
||||
Regex::new(r"^\s*@deno-types\s?=\s?(\S+)\s*(.*)\s*$").unwrap();
|
||||
// These regexes were adapted from TypeScript
|
||||
|
@ -199,6 +199,19 @@ lazy_static! {
|
|||
Regex::new(r#"(\slib\s*=\s*)('|")(.+?)('|")"#).unwrap();
|
||||
}
|
||||
|
||||
fn warn_ignored_options(
|
||||
maybe_ignored_options: Option<tsc_config::IgnoredCompilerOptions>,
|
||||
config_path: &Path,
|
||||
) {
|
||||
if let Some(ignored_options) = maybe_ignored_options {
|
||||
eprintln!(
|
||||
"Unsupported compiler options in \"{}\"\n The following options were ignored:\n {}",
|
||||
config_path.to_string_lossy(),
|
||||
ignored_options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new worker with snapshot of TS compiler and setup compiler's
|
||||
/// runtime.
|
||||
fn create_compiler_worker(
|
||||
|
@ -241,70 +254,65 @@ pub enum TargetLib {
|
|||
#[derive(Clone)]
|
||||
pub struct CompilerConfig {
|
||||
pub path: Option<PathBuf>,
|
||||
pub content: Option<Vec<u8>>,
|
||||
pub hash: Vec<u8>,
|
||||
pub options: Value,
|
||||
pub maybe_ignored_options: Option<tsc_config::IgnoredCompilerOptions>,
|
||||
pub hash: String,
|
||||
pub compile_js: bool,
|
||||
}
|
||||
|
||||
impl CompilerConfig {
|
||||
/// Take the passed flag and resolve the file name relative to the cwd.
|
||||
pub fn load(config_path: Option<String>) -> Result<Self, ErrBox> {
|
||||
let config_file = match &config_path {
|
||||
Some(config_file_name) => {
|
||||
debug!("Compiler config file: {}", config_file_name);
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
Some(cwd.join(config_file_name))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
pub fn load(maybe_config_path: Option<String>) -> Result<Self, ErrBox> {
|
||||
if maybe_config_path.is_none() {
|
||||
return Ok(Self {
|
||||
path: Some(PathBuf::new()),
|
||||
options: json!({}),
|
||||
maybe_ignored_options: None,
|
||||
hash: "".to_string(),
|
||||
compile_js: false,
|
||||
});
|
||||
}
|
||||
|
||||
let raw_config_path = maybe_config_path.unwrap();
|
||||
debug!("Compiler config file: {}", raw_config_path);
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let config_file = cwd.join(raw_config_path);
|
||||
|
||||
// Convert the PathBuf to a canonicalized string. This is needed by the
|
||||
// compiler to properly deal with the configuration.
|
||||
let config_path = match &config_file {
|
||||
Some(config_file) => Some(config_file.canonicalize().map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"Could not find the config file: {}",
|
||||
config_file.to_string_lossy()
|
||||
),
|
||||
)
|
||||
})),
|
||||
_ => None,
|
||||
};
|
||||
let config_path = config_file.canonicalize().map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"Could not find the config file: {}",
|
||||
config_file.to_string_lossy()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Load the contents of the configuration file
|
||||
let config = match &config_file {
|
||||
Some(config_file) => {
|
||||
debug!("Attempt to load config: {}", config_file.to_str().unwrap());
|
||||
let config = fs::read(&config_file)?;
|
||||
Some(config)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
debug!("Attempt to load config: {}", config_path.to_str().unwrap());
|
||||
let config_bytes = fs::read(&config_file)?;
|
||||
let config_hash = crate::checksum::gen(&[&config_bytes]);
|
||||
let config_str = String::from_utf8(config_bytes)?;
|
||||
|
||||
let config_hash = match &config {
|
||||
Some(bytes) => bytes.clone(),
|
||||
_ => b"".to_vec(),
|
||||
let (options, maybe_ignored_options) = if config_str.is_empty() {
|
||||
(json!({}), None)
|
||||
} else {
|
||||
tsc_config::parse_config(&config_str)?
|
||||
};
|
||||
|
||||
// If `checkJs` is set to true in `compilerOptions` then we're gonna be compiling
|
||||
// JavaScript files as well
|
||||
let compile_js = if let Some(config_content) = config.clone() {
|
||||
let config_str = std::str::from_utf8(&config_content)?;
|
||||
CHECK_JS_RE.is_match(config_str)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let compile_js = options["checkJs"].as_bool().unwrap_or(false);
|
||||
|
||||
let ts_config = Self {
|
||||
path: config_path.unwrap_or_else(|| Ok(PathBuf::new())).ok(),
|
||||
content: config,
|
||||
Ok(Self {
|
||||
path: Some(config_path),
|
||||
options,
|
||||
maybe_ignored_options,
|
||||
hash: config_hash,
|
||||
compile_js,
|
||||
};
|
||||
|
||||
Ok(ts_config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,7 +479,7 @@ impl TsCompiler {
|
|||
let version_hash_to_validate = source_code_version_hash(
|
||||
&source_file.source_code.as_bytes(),
|
||||
version::DENO,
|
||||
&self.config.hash,
|
||||
&self.config.hash.as_bytes(),
|
||||
);
|
||||
|
||||
if metadata.version_hash == version_hash_to_validate {
|
||||
|
@ -577,40 +585,58 @@ impl TsCompiler {
|
|||
let unstable = self.flags.unstable;
|
||||
let performance = matches!(self.flags.log_level, Some(Level::Debug));
|
||||
let compiler_config = self.config.clone();
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
|
||||
let j = match (compiler_config.path, compiler_config.content) {
|
||||
(Some(config_path), Some(config_data)) => json!({
|
||||
"type": msg::CompilerRequestType::Compile,
|
||||
"allowJs": allow_js,
|
||||
"target": target,
|
||||
"rootNames": root_names,
|
||||
"unstable": unstable,
|
||||
"performance": performance,
|
||||
"configPath": config_path,
|
||||
"config": str::from_utf8(&config_data).unwrap(),
|
||||
"cwd": cwd,
|
||||
"sourceFileMap": module_graph_json,
|
||||
"buildInfo": if self.use_disk_cache { build_info } else { None },
|
||||
}),
|
||||
_ => json!({
|
||||
"type": msg::CompilerRequestType::Compile,
|
||||
"allowJs": allow_js,
|
||||
"target": target,
|
||||
"rootNames": root_names,
|
||||
"unstable": unstable,
|
||||
"performance": performance,
|
||||
"cwd": cwd,
|
||||
"sourceFileMap": module_graph_json,
|
||||
"buildInfo": if self.use_disk_cache { build_info } else { None },
|
||||
}),
|
||||
};
|
||||
|
||||
let req_msg = j.to_string();
|
||||
|
||||
// TODO(bartlomieju): lift this call up - TSC shouldn't print anything
|
||||
info!("{} {}", colors::green("Check"), module_url.to_string());
|
||||
|
||||
let mut lib = if target == "main" {
|
||||
vec!["deno.window"]
|
||||
} else {
|
||||
vec!["deno.worker"]
|
||||
};
|
||||
|
||||
if unstable {
|
||||
lib.push("deno.unstable");
|
||||
}
|
||||
|
||||
let mut compiler_options = json!({
|
||||
"allowJs": allow_js,
|
||||
"allowNonTsExtensions": true,
|
||||
"checkJs": false,
|
||||
"esModuleInterop": true,
|
||||
"incremental": true,
|
||||
"inlineSourceMap": true,
|
||||
"jsx": "react",
|
||||
"lib": lib,
|
||||
"module": "esnext",
|
||||
"outDir": "deno://",
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"removeComments": true,
|
||||
"target": "esnext",
|
||||
"tsBuildInfoFile": "cache:///tsbuildinfo.json",
|
||||
});
|
||||
|
||||
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 j = json!({
|
||||
"type": msg::CompilerRequestType::Compile,
|
||||
"target": target,
|
||||
"rootNames": root_names,
|
||||
"performance": performance,
|
||||
"compilerOptions": compiler_options,
|
||||
"sourceFileMap": module_graph_json,
|
||||
"buildInfo": if self.use_disk_cache { build_info } else { None },
|
||||
});
|
||||
|
||||
let req_msg = j.to_string();
|
||||
|
||||
let json_str =
|
||||
execute_in_same_thread(global_state, permissions, req_msg).await?;
|
||||
|
||||
|
@ -680,36 +706,54 @@ impl TsCompiler {
|
|||
|
||||
let root_names = vec![module_specifier.to_string()];
|
||||
let target = "main";
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let performance =
|
||||
matches!(global_state.flags.log_level, Some(Level::Debug));
|
||||
let unstable = self.flags.unstable;
|
||||
|
||||
let mut lib = if target == "main" {
|
||||
vec!["deno.window"]
|
||||
} else {
|
||||
vec!["deno.worker"]
|
||||
};
|
||||
|
||||
if unstable {
|
||||
lib.push("deno.unstable");
|
||||
}
|
||||
|
||||
let mut compiler_options = json!({
|
||||
"allowJs": true,
|
||||
"allowNonTsExtensions": true,
|
||||
"checkJs": false,
|
||||
"esModuleInterop": true,
|
||||
"inlineSourceMap": false,
|
||||
"jsx": "react",
|
||||
"lib": lib,
|
||||
"module": "system",
|
||||
"outFile": "deno:///bundle.js",
|
||||
// disabled until we have effective way to modify source maps
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"removeComments": true,
|
||||
"target": "esnext",
|
||||
});
|
||||
|
||||
let compiler_config = self.config.clone();
|
||||
|
||||
// TODO(bartlomieju): this is non-sense; CompilerConfig's `path` and `content` should
|
||||
// be optional
|
||||
let j = match (compiler_config.path, compiler_config.content) {
|
||||
(Some(config_path), Some(config_data)) => json!({
|
||||
"type": msg::CompilerRequestType::Bundle,
|
||||
"target": target,
|
||||
"rootNames": root_names,
|
||||
"unstable": self.flags.unstable,
|
||||
"performance": performance,
|
||||
"configPath": config_path,
|
||||
"config": str::from_utf8(&config_data).unwrap(),
|
||||
"cwd": cwd,
|
||||
"sourceFileMap": module_graph_json,
|
||||
}),
|
||||
_ => json!({
|
||||
"type": msg::CompilerRequestType::Bundle,
|
||||
"target": target,
|
||||
"rootNames": root_names,
|
||||
"unstable": self.flags.unstable,
|
||||
"performance": performance,
|
||||
"cwd": cwd,
|
||||
"sourceFileMap": module_graph_json,
|
||||
}),
|
||||
};
|
||||
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 j = json!({
|
||||
"type": msg::CompilerRequestType::Bundle,
|
||||
"target": target,
|
||||
"rootNames": root_names,
|
||||
"performance": performance,
|
||||
"compilerOptions": compiler_options,
|
||||
"sourceFileMap": module_graph_json,
|
||||
});
|
||||
|
||||
let req_msg = j.to_string();
|
||||
|
||||
|
@ -900,7 +944,7 @@ impl TsCompiler {
|
|||
let version_hash = source_code_version_hash(
|
||||
&source_file.source_code.as_bytes(),
|
||||
version::DENO,
|
||||
&self.config.hash,
|
||||
&self.config.hash.as_bytes(),
|
||||
);
|
||||
|
||||
let compiled_file_metadata = CompiledFileMetadata { version_hash };
|
||||
|
@ -1069,7 +1113,7 @@ async fn create_runtime_module_graph(
|
|||
permissions: Permissions,
|
||||
root_name: &str,
|
||||
sources: &Option<HashMap<String, String>>,
|
||||
maybe_options: &Option<String>,
|
||||
type_files: Vec<String>,
|
||||
) -> Result<(Vec<String>, ModuleGraph), ErrBox> {
|
||||
let mut root_names = vec![];
|
||||
let mut module_graph_loader = ModuleGraphLoader::new(
|
||||
|
@ -1093,23 +1137,12 @@ async fn create_runtime_module_graph(
|
|||
}
|
||||
|
||||
// download all additional files from TSconfig and add them to root_names
|
||||
if let Some(options) = maybe_options {
|
||||
let options_json: serde_json::Value = serde_json::from_str(options)?;
|
||||
if let Some(types_option) = options_json.get("types") {
|
||||
let types_arr = types_option.as_array().expect("types is not an array");
|
||||
|
||||
for type_value in types_arr {
|
||||
let type_str = type_value
|
||||
.as_str()
|
||||
.expect("type is not a string")
|
||||
.to_string();
|
||||
let type_specifier = ModuleSpecifier::resolve_url_or_path(&type_str)?;
|
||||
module_graph_loader
|
||||
.add_to_graph(&type_specifier, None)
|
||||
.await?;
|
||||
root_names.push(type_specifier.to_string())
|
||||
}
|
||||
}
|
||||
for type_file in type_files {
|
||||
let type_specifier = ModuleSpecifier::resolve_url_or_path(&type_file)?;
|
||||
module_graph_loader
|
||||
.add_to_graph(&type_specifier, None)
|
||||
.await?;
|
||||
root_names.push(type_specifier.to_string())
|
||||
}
|
||||
|
||||
Ok((root_names, module_graph_loader.get_graph()))
|
||||
|
@ -1135,12 +1168,65 @@ pub async fn runtime_compile(
|
|||
sources: &Option<HashMap<String, String>>,
|
||||
maybe_options: &Option<String>,
|
||||
) -> Result<Value, ErrBox> {
|
||||
let mut user_options = if let Some(options) = maybe_options {
|
||||
tsc_config::parse_raw_config(options)?
|
||||
} else {
|
||||
json!({})
|
||||
};
|
||||
|
||||
// Intentionally calling "take()" to replace value with `null` - otherwise TSC will try to load that file
|
||||
// using `fileExists` API
|
||||
let type_files = if let Some(types) = user_options["types"].take().as_array()
|
||||
{
|
||||
types
|
||||
.iter()
|
||||
.map(|type_value| type_value.as_str().unwrap_or("").to_string())
|
||||
.filter(|type_str| !type_str.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let unstable = global_state.flags.unstable;
|
||||
|
||||
let mut lib = vec![];
|
||||
if let Some(user_libs) = user_options["lib"].take().as_array() {
|
||||
let libs = user_libs
|
||||
.iter()
|
||||
.map(|type_value| type_value.as_str().unwrap_or("").to_string())
|
||||
.filter(|type_str| !type_str.is_empty())
|
||||
.collect::<Vec<String>>();
|
||||
lib.extend(libs);
|
||||
} else {
|
||||
lib.push("deno.window".to_string());
|
||||
}
|
||||
|
||||
if unstable {
|
||||
lib.push("deno.unstable".to_string());
|
||||
}
|
||||
|
||||
let mut compiler_options = json!({
|
||||
"allowJs": false,
|
||||
"allowNonTsExtensions": true,
|
||||
"checkJs": false,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"module": "esnext",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"removeComments": true,
|
||||
"target": "esnext",
|
||||
});
|
||||
|
||||
tsc_config::json_merge(&mut compiler_options, &user_options);
|
||||
tsc_config::json_merge(&mut compiler_options, &json!({ "lib": lib }));
|
||||
|
||||
let (root_names, module_graph) = create_runtime_module_graph(
|
||||
&global_state,
|
||||
permissions.clone(),
|
||||
root_name,
|
||||
sources,
|
||||
maybe_options,
|
||||
type_files,
|
||||
)
|
||||
.await?;
|
||||
let module_graph_json =
|
||||
|
@ -1151,8 +1237,7 @@ pub async fn runtime_compile(
|
|||
"target": "runtime",
|
||||
"rootNames": root_names,
|
||||
"sourceFileMap": module_graph_json,
|
||||
"options": maybe_options,
|
||||
"unstable": global_state.flags.unstable,
|
||||
"compilerOptions": compiler_options,
|
||||
})
|
||||
.to_string();
|
||||
|
||||
|
@ -1182,24 +1267,88 @@ pub async fn runtime_bundle(
|
|||
sources: &Option<HashMap<String, String>>,
|
||||
maybe_options: &Option<String>,
|
||||
) -> Result<Value, ErrBox> {
|
||||
let mut user_options = if let Some(options) = maybe_options {
|
||||
tsc_config::parse_raw_config(options)?
|
||||
} else {
|
||||
json!({})
|
||||
};
|
||||
|
||||
// Intentionally calling "take()" to replace value with `null` - otherwise TSC will try to load that file
|
||||
// using `fileExists` API
|
||||
let type_files = if let Some(types) = user_options["types"].take().as_array()
|
||||
{
|
||||
types
|
||||
.iter()
|
||||
.map(|type_value| type_value.as_str().unwrap_or("").to_string())
|
||||
.filter(|type_str| !type_str.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let (root_names, module_graph) = create_runtime_module_graph(
|
||||
&global_state,
|
||||
permissions.clone(),
|
||||
root_name,
|
||||
sources,
|
||||
maybe_options,
|
||||
type_files,
|
||||
)
|
||||
.await?;
|
||||
let module_graph_json =
|
||||
serde_json::to_value(module_graph).expect("Failed to serialize data");
|
||||
|
||||
let unstable = global_state.flags.unstable;
|
||||
|
||||
let mut lib = vec![];
|
||||
if let Some(user_libs) = user_options["lib"].take().as_array() {
|
||||
let libs = user_libs
|
||||
.iter()
|
||||
.map(|type_value| type_value.as_str().unwrap_or("").to_string())
|
||||
.filter(|type_str| !type_str.is_empty())
|
||||
.collect::<Vec<String>>();
|
||||
lib.extend(libs);
|
||||
} else {
|
||||
lib.push("deno.window".to_string());
|
||||
}
|
||||
|
||||
if unstable {
|
||||
lib.push("deno.unstable".to_string());
|
||||
}
|
||||
|
||||
let mut compiler_options = json!({
|
||||
"allowJs": false,
|
||||
"allowNonTsExtensions": true,
|
||||
"checkJs": false,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"module": "esnext",
|
||||
"outDir": null,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"removeComments": true,
|
||||
"target": "esnext",
|
||||
});
|
||||
|
||||
let bundler_options = json!({
|
||||
"allowJs": true,
|
||||
"inlineSourceMap": false,
|
||||
"module": "system",
|
||||
"outDir": null,
|
||||
"outFile": "deno:///bundle.js",
|
||||
// disabled until we have effective way to modify source maps
|
||||
"sourceMap": false,
|
||||
});
|
||||
|
||||
tsc_config::json_merge(&mut compiler_options, &user_options);
|
||||
tsc_config::json_merge(&mut compiler_options, &json!({ "lib": lib }));
|
||||
tsc_config::json_merge(&mut compiler_options, &bundler_options);
|
||||
|
||||
let req_msg = json!({
|
||||
"type": msg::CompilerRequestType::RuntimeBundle,
|
||||
"target": "runtime",
|
||||
"rootNames": root_names,
|
||||
"sourceFileMap": module_graph_json,
|
||||
"options": maybe_options,
|
||||
"unstable": global_state.flags.unstable,
|
||||
"compilerOptions": compiler_options,
|
||||
})
|
||||
.to_string();
|
||||
|
||||
|
@ -1218,12 +1367,27 @@ pub async fn runtime_transpile(
|
|||
global_state: &Arc<GlobalState>,
|
||||
permissions: Permissions,
|
||||
sources: &HashMap<String, String>,
|
||||
options: &Option<String>,
|
||||
maybe_options: &Option<String>,
|
||||
) -> Result<Value, ErrBox> {
|
||||
let user_options = if let Some(options) = maybe_options {
|
||||
tsc_config::parse_raw_config(options)?
|
||||
} else {
|
||||
json!({})
|
||||
};
|
||||
|
||||
let mut compiler_options = json!({
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"sourceMap": true,
|
||||
"scriptComments": true,
|
||||
"target": "esnext",
|
||||
});
|
||||
tsc_config::json_merge(&mut compiler_options, &user_options);
|
||||
|
||||
let req_msg = json!({
|
||||
"type": msg::CompilerRequestType::RuntimeTranspile,
|
||||
"sources": sources,
|
||||
"options": options,
|
||||
"compilerOptions": compiler_options,
|
||||
})
|
||||
.to_string();
|
||||
|
||||
|
@ -1754,11 +1918,14 @@ mod tests {
|
|||
(r#"{ "compilerOptions": { "checkJs": true } } "#, true),
|
||||
// JSON with comment
|
||||
(
|
||||
r#"{ "compilerOptions": { // force .js file compilation by Deno "checkJs": true } } "#,
|
||||
r#"{
|
||||
"compilerOptions": {
|
||||
// force .js file compilation by Deno
|
||||
"checkJs": true
|
||||
}
|
||||
}"#,
|
||||
true,
|
||||
),
|
||||
// invalid JSON
|
||||
(r#"{ "compilerOptions": { "checkJs": true },{ } "#, true),
|
||||
// without content
|
||||
("", false),
|
||||
];
|
||||
|
|
|
@ -303,95 +303,6 @@ delete Object.prototype.__proto__;
|
|||
// file are passed back to Rust and saved to $DENO_DIR.
|
||||
const TS_BUILD_INFO = "cache:///tsbuildinfo.json";
|
||||
|
||||
// TODO(Bartlomieju): this check should be done in Rust
|
||||
const IGNORED_COMPILER_OPTIONS = [
|
||||
"allowSyntheticDefaultImports",
|
||||
"allowUmdGlobalAccess",
|
||||
"assumeChangesOnlyAffectDirectDependencies",
|
||||
"baseUrl",
|
||||
"build",
|
||||
"composite",
|
||||
"declaration",
|
||||
"declarationDir",
|
||||
"declarationMap",
|
||||
"diagnostics",
|
||||
"downlevelIteration",
|
||||
"emitBOM",
|
||||
"emitDeclarationOnly",
|
||||
"esModuleInterop",
|
||||
"extendedDiagnostics",
|
||||
"forceConsistentCasingInFileNames",
|
||||
"generateCpuProfile",
|
||||
"help",
|
||||
"importHelpers",
|
||||
"incremental",
|
||||
"inlineSourceMap",
|
||||
"inlineSources",
|
||||
"init",
|
||||
"listEmittedFiles",
|
||||
"listFiles",
|
||||
"mapRoot",
|
||||
"maxNodeModuleJsDepth",
|
||||
"module",
|
||||
"moduleResolution",
|
||||
"newLine",
|
||||
"noEmit",
|
||||
"noEmitHelpers",
|
||||
"noEmitOnError",
|
||||
"noLib",
|
||||
"noResolve",
|
||||
"out",
|
||||
"outDir",
|
||||
"outFile",
|
||||
"paths",
|
||||
"preserveSymlinks",
|
||||
"preserveWatchOutput",
|
||||
"pretty",
|
||||
"rootDir",
|
||||
"rootDirs",
|
||||
"showConfig",
|
||||
"skipDefaultLibCheck",
|
||||
"skipLibCheck",
|
||||
"sourceMap",
|
||||
"sourceRoot",
|
||||
"stripInternal",
|
||||
"target",
|
||||
"traceResolution",
|
||||
"tsBuildInfoFile",
|
||||
"types",
|
||||
"typeRoots",
|
||||
"version",
|
||||
"watch",
|
||||
];
|
||||
|
||||
const DEFAULT_BUNDLER_OPTIONS = {
|
||||
allowJs: true,
|
||||
inlineSourceMap: false,
|
||||
module: ts.ModuleKind.System,
|
||||
outDir: undefined,
|
||||
outFile: `${OUT_DIR}/bundle.js`,
|
||||
// disabled until we have effective way to modify source maps
|
||||
sourceMap: false,
|
||||
};
|
||||
|
||||
const DEFAULT_INCREMENTAL_COMPILE_OPTIONS = {
|
||||
allowJs: false,
|
||||
allowNonTsExtensions: true,
|
||||
checkJs: false,
|
||||
esModuleInterop: true,
|
||||
incremental: true,
|
||||
inlineSourceMap: true,
|
||||
jsx: ts.JsxEmit.React,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
outDir: OUT_DIR,
|
||||
resolveJsonModule: true,
|
||||
sourceMap: false,
|
||||
strict: true,
|
||||
stripComments: true,
|
||||
target: ts.ScriptTarget.ESNext,
|
||||
tsBuildInfoFile: TS_BUILD_INFO,
|
||||
};
|
||||
|
||||
const DEFAULT_COMPILE_OPTIONS = {
|
||||
allowJs: false,
|
||||
allowNonTsExtensions: true,
|
||||
|
@ -406,18 +317,6 @@ delete Object.prototype.__proto__;
|
|||
target: ts.ScriptTarget.ESNext,
|
||||
};
|
||||
|
||||
const DEFAULT_RUNTIME_COMPILE_OPTIONS = {
|
||||
outDir: undefined,
|
||||
};
|
||||
|
||||
const DEFAULT_RUNTIME_TRANSPILE_OPTIONS = {
|
||||
esModuleInterop: true,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
sourceMap: true,
|
||||
scriptComments: true,
|
||||
target: ts.ScriptTarget.ESNext,
|
||||
};
|
||||
|
||||
const CompilerHostTarget = {
|
||||
Main: "main",
|
||||
Runtime: "runtime",
|
||||
|
@ -481,28 +380,17 @@ delete Object.prototype.__proto__;
|
|||
*/
|
||||
const RESOLVED_SPECIFIER_CACHE = new Map();
|
||||
|
||||
function configure(defaultOptions, source, path, cwd) {
|
||||
const { config, error } = ts.parseConfigFileTextToJson(path, source);
|
||||
if (error) {
|
||||
return { diagnostics: [error], options: defaultOptions };
|
||||
}
|
||||
function parseCompilerOptions(compilerOptions) {
|
||||
// TODO(bartlomieju): using `/` and `/tsconfig.json` because
|
||||
// otherwise TSC complains that some paths are relative
|
||||
// and some are absolute
|
||||
const { options, errors } = ts.convertCompilerOptionsFromJson(
|
||||
config.compilerOptions,
|
||||
cwd,
|
||||
compilerOptions,
|
||||
"/",
|
||||
"/tsconfig.json",
|
||||
);
|
||||
const ignoredOptions = [];
|
||||
for (const key of Object.keys(options)) {
|
||||
if (
|
||||
IGNORED_COMPILER_OPTIONS.includes(key) &&
|
||||
(!(key in defaultOptions) || options[key] !== defaultOptions[key])
|
||||
) {
|
||||
ignoredOptions.push(key);
|
||||
delete options[key];
|
||||
}
|
||||
}
|
||||
return {
|
||||
options: Object.assign({}, defaultOptions, options),
|
||||
ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
|
||||
options,
|
||||
diagnostics: errors.length ? errors : undefined,
|
||||
};
|
||||
}
|
||||
|
@ -567,57 +455,25 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
|
||||
class Host {
|
||||
#options = DEFAULT_COMPILE_OPTIONS;
|
||||
#target = "";
|
||||
#writeFile = null;
|
||||
#options;
|
||||
#target;
|
||||
#writeFile;
|
||||
/* Deno specific APIs */
|
||||
|
||||
constructor({
|
||||
bundle = false,
|
||||
incremental = false,
|
||||
constructor(
|
||||
options,
|
||||
target,
|
||||
unstable,
|
||||
writeFile,
|
||||
}) {
|
||||
) {
|
||||
this.#target = target;
|
||||
this.#writeFile = writeFile;
|
||||
if (bundle) {
|
||||
// options we need to change when we are generating a bundle
|
||||
Object.assign(this.#options, DEFAULT_BUNDLER_OPTIONS);
|
||||
} else if (incremental) {
|
||||
Object.assign(this.#options, DEFAULT_INCREMENTAL_COMPILE_OPTIONS);
|
||||
}
|
||||
if (unstable) {
|
||||
this.#options.lib = [
|
||||
target === CompilerHostTarget.Worker
|
||||
? "lib.deno.worker.d.ts"
|
||||
: "lib.deno.window.d.ts",
|
||||
"lib.deno.unstable.d.ts",
|
||||
];
|
||||
}
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
configure(cwd, path, configurationText) {
|
||||
log("compiler::host.configure", path);
|
||||
const { options, ...result } = configure(
|
||||
this.#options,
|
||||
configurationText,
|
||||
path,
|
||||
cwd,
|
||||
);
|
||||
this.#options = options;
|
||||
return result;
|
||||
}
|
||||
|
||||
mergeOptions(...options) {
|
||||
Object.assign(this.#options, ...options);
|
||||
return Object.assign({}, this.#options);
|
||||
}
|
||||
|
||||
/* TypeScript CompilerHost APIs */
|
||||
|
||||
fileExists(_fileName) {
|
||||
|
@ -742,9 +598,13 @@ delete Object.prototype.__proto__;
|
|||
class IncrementalCompileHost extends Host {
|
||||
#buildInfo = "";
|
||||
|
||||
constructor(options) {
|
||||
super({ ...options, incremental: true });
|
||||
const { buildInfo } = options;
|
||||
constructor(
|
||||
options,
|
||||
target,
|
||||
writeFile,
|
||||
buildInfo,
|
||||
) {
|
||||
super(options, target, writeFile);
|
||||
if (buildInfo) {
|
||||
this.#buildInfo = buildInfo;
|
||||
}
|
||||
|
@ -761,10 +621,11 @@ delete Object.prototype.__proto__;
|
|||
// NOTE: target doesn't really matter here,
|
||||
// this is in fact a mock host created just to
|
||||
// load all type definitions and snapshot them.
|
||||
let SNAPSHOT_HOST = new Host({
|
||||
target: CompilerHostTarget.Main,
|
||||
writeFile() {},
|
||||
});
|
||||
let SNAPSHOT_HOST = new Host(
|
||||
DEFAULT_COMPILE_OPTIONS,
|
||||
CompilerHostTarget.Main,
|
||||
() => {},
|
||||
);
|
||||
const SNAPSHOT_COMPILER_OPTIONS = SNAPSHOT_HOST.getCompilationSettings();
|
||||
|
||||
// This is a hacky way of adding our libs to the libs available in TypeScript()
|
||||
|
@ -985,101 +846,7 @@ delete Object.prototype.__proto__;
|
|||
};
|
||||
}
|
||||
|
||||
function convertCompilerOptions(str) {
|
||||
const options = JSON.parse(str);
|
||||
const out = {};
|
||||
const keys = Object.keys(options);
|
||||
const files = [];
|
||||
for (const key of keys) {
|
||||
switch (key) {
|
||||
case "jsx":
|
||||
const value = options[key];
|
||||
if (value === "preserve") {
|
||||
out[key] = ts.JsxEmit.Preserve;
|
||||
} else if (value === "react") {
|
||||
out[key] = ts.JsxEmit.React;
|
||||
} else {
|
||||
out[key] = ts.JsxEmit.ReactNative;
|
||||
}
|
||||
break;
|
||||
case "module":
|
||||
switch (options[key]) {
|
||||
case "amd":
|
||||
out[key] = ts.ModuleKind.AMD;
|
||||
break;
|
||||
case "commonjs":
|
||||
out[key] = ts.ModuleKind.CommonJS;
|
||||
break;
|
||||
case "es2015":
|
||||
case "es6":
|
||||
out[key] = ts.ModuleKind.ES2015;
|
||||
break;
|
||||
case "esnext":
|
||||
out[key] = ts.ModuleKind.ESNext;
|
||||
break;
|
||||
case "none":
|
||||
out[key] = ts.ModuleKind.None;
|
||||
break;
|
||||
case "system":
|
||||
out[key] = ts.ModuleKind.System;
|
||||
break;
|
||||
case "umd":
|
||||
out[key] = ts.ModuleKind.UMD;
|
||||
break;
|
||||
default:
|
||||
throw new TypeError("Unexpected module type");
|
||||
}
|
||||
break;
|
||||
case "target":
|
||||
switch (options[key]) {
|
||||
case "es3":
|
||||
out[key] = ts.ScriptTarget.ES3;
|
||||
break;
|
||||
case "es5":
|
||||
out[key] = ts.ScriptTarget.ES5;
|
||||
break;
|
||||
case "es6":
|
||||
case "es2015":
|
||||
out[key] = ts.ScriptTarget.ES2015;
|
||||
break;
|
||||
case "es2016":
|
||||
out[key] = ts.ScriptTarget.ES2016;
|
||||
break;
|
||||
case "es2017":
|
||||
out[key] = ts.ScriptTarget.ES2017;
|
||||
break;
|
||||
case "es2018":
|
||||
out[key] = ts.ScriptTarget.ES2018;
|
||||
break;
|
||||
case "es2019":
|
||||
out[key] = ts.ScriptTarget.ES2019;
|
||||
break;
|
||||
case "es2020":
|
||||
out[key] = ts.ScriptTarget.ES2020;
|
||||
break;
|
||||
case "esnext":
|
||||
out[key] = ts.ScriptTarget.ESNext;
|
||||
break;
|
||||
default:
|
||||
throw new TypeError("Unexpected emit target.");
|
||||
}
|
||||
break;
|
||||
case "types":
|
||||
const types = options[key];
|
||||
assert(types);
|
||||
files.push(...types);
|
||||
break;
|
||||
default:
|
||||
out[key] = options[key];
|
||||
}
|
||||
}
|
||||
return {
|
||||
options: out,
|
||||
files: files.length ? files : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const ignoredDiagnostics = [
|
||||
const IGNORED_DIAGNOSTICS = [
|
||||
// TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
|
||||
// not a module.
|
||||
2306,
|
||||
|
@ -1158,21 +925,6 @@ delete Object.prototype.__proto__;
|
|||
return stats;
|
||||
}
|
||||
|
||||
// TODO(Bartlomieju): this check should be done in Rust; there should be no
|
||||
function processConfigureResponse(configResult, configPath) {
|
||||
const { ignoredOptions, diagnostics } = configResult;
|
||||
if (ignoredOptions) {
|
||||
const msg =
|
||||
`Unsupported compiler options in "${configPath}"\n The following options were ignored:\n ${
|
||||
ignoredOptions
|
||||
.map((value) => value)
|
||||
.join(", ")
|
||||
}\n`;
|
||||
core.print(msg, true);
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
function normalizeString(path) {
|
||||
let res = "";
|
||||
let lastSegmentLength = 0;
|
||||
|
@ -1346,14 +1098,10 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
|
||||
function compile({
|
||||
allowJs,
|
||||
buildInfo,
|
||||
config,
|
||||
configPath,
|
||||
compilerOptions,
|
||||
rootNames,
|
||||
target,
|
||||
unstable,
|
||||
cwd,
|
||||
sourceFileMap,
|
||||
type,
|
||||
performance,
|
||||
|
@ -1371,23 +1119,27 @@ delete Object.prototype.__proto__;
|
|||
rootNames,
|
||||
emitMap: {},
|
||||
};
|
||||
const host = new IncrementalCompileHost({
|
||||
bundle: false,
|
||||
target,
|
||||
unstable,
|
||||
writeFile: createCompileWriteFile(state),
|
||||
rootNames,
|
||||
buildInfo,
|
||||
});
|
||||
|
||||
let diagnostics = [];
|
||||
|
||||
host.mergeOptions({ allowJs });
|
||||
const { options, diagnostics: diags } = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
|
||||
// if there is a configuration supplied, we need to parse that
|
||||
if (config && config.length && configPath) {
|
||||
const configResult = host.configure(cwd, configPath, config);
|
||||
diagnostics = processConfigureResponse(configResult, configPath) || [];
|
||||
}
|
||||
diagnostics = diags.filter(
|
||||
({ code }) => code != 5023 && !IGNORED_DIAGNOSTICS.includes(code),
|
||||
);
|
||||
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
const host = new IncrementalCompileHost(
|
||||
options,
|
||||
target,
|
||||
createCompileWriteFile(state),
|
||||
buildInfo,
|
||||
);
|
||||
|
||||
buildSourceFileCache(sourceFileMap);
|
||||
// if there was a configuration and no diagnostics with it, we will continue
|
||||
|
@ -1409,7 +1161,7 @@ delete Object.prototype.__proto__;
|
|||
...program.getSemanticDiagnostics(),
|
||||
];
|
||||
diagnostics = diagnostics.filter(
|
||||
({ code }) => !ignoredDiagnostics.includes(code),
|
||||
({ code }) => !IGNORED_DIAGNOSTICS.includes(code),
|
||||
);
|
||||
|
||||
// We will only proceed with the emit if there are no diagnostics.
|
||||
|
@ -1443,12 +1195,9 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
|
||||
function bundle({
|
||||
config,
|
||||
configPath,
|
||||
compilerOptions,
|
||||
rootNames,
|
||||
target,
|
||||
unstable,
|
||||
cwd,
|
||||
sourceFileMap,
|
||||
type,
|
||||
performance,
|
||||
|
@ -1469,20 +1218,25 @@ delete Object.prototype.__proto__;
|
|||
rootNames,
|
||||
bundleOutput: undefined,
|
||||
};
|
||||
const host = new Host({
|
||||
bundle: true,
|
||||
target,
|
||||
unstable,
|
||||
writeFile: createBundleWriteFile(state),
|
||||
});
|
||||
state.host = host;
|
||||
let diagnostics = [];
|
||||
|
||||
// if there is a configuration supplied, we need to parse that
|
||||
if (config && config.length && configPath) {
|
||||
const configResult = host.configure(cwd, configPath, config);
|
||||
diagnostics = processConfigureResponse(configResult, configPath) || [];
|
||||
}
|
||||
const { options, diagnostics: diags } = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
|
||||
diagnostics = diags.filter(
|
||||
({ code }) => code != 5023 && !IGNORED_DIAGNOSTICS.includes(code),
|
||||
);
|
||||
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
const host = new Host(
|
||||
options,
|
||||
target,
|
||||
createBundleWriteFile(state),
|
||||
);
|
||||
state.host = host;
|
||||
|
||||
buildSourceFileCache(sourceFileMap);
|
||||
// if there was a configuration and no diagnostics with it, we will continue
|
||||
|
@ -1497,7 +1251,7 @@ delete Object.prototype.__proto__;
|
|||
|
||||
diagnostics = ts
|
||||
.getPreEmitDiagnostics(program)
|
||||
.filter(({ code }) => !ignoredDiagnostics.includes(code));
|
||||
.filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
|
||||
|
||||
// We will only proceed with the emit if there are no diagnostics.
|
||||
if (diagnostics.length === 0) {
|
||||
|
@ -1542,7 +1296,7 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
|
||||
function runtimeCompile(request) {
|
||||
const { options, rootNames, target, unstable, sourceFileMap } = request;
|
||||
const { compilerOptions, rootNames, target, sourceFileMap } = request;
|
||||
|
||||
log(">>> runtime compile start", {
|
||||
rootNames,
|
||||
|
@ -1550,11 +1304,13 @@ delete Object.prototype.__proto__;
|
|||
|
||||
// if there are options, convert them into TypeScript compiler options,
|
||||
// and resolve any external file references
|
||||
let convertedOptions;
|
||||
if (options) {
|
||||
const result = convertCompilerOptions(options);
|
||||
convertedOptions = result.options;
|
||||
}
|
||||
const result = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
const options = result.options;
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
buildLocalSourceFileCache(sourceFileMap);
|
||||
|
||||
|
@ -1562,25 +1318,11 @@ delete Object.prototype.__proto__;
|
|||
rootNames,
|
||||
emitMap: {},
|
||||
};
|
||||
const host = new Host({
|
||||
bundle: false,
|
||||
const host = new Host(
|
||||
options,
|
||||
target,
|
||||
writeFile: createRuntimeCompileWriteFile(state),
|
||||
});
|
||||
const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS];
|
||||
if (convertedOptions) {
|
||||
compilerOptions.push(convertedOptions);
|
||||
}
|
||||
if (unstable) {
|
||||
compilerOptions.push({
|
||||
lib: [
|
||||
"deno.unstable",
|
||||
...((convertedOptions && convertedOptions.lib) || ["deno.window"]),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
host.mergeOptions(...compilerOptions);
|
||||
createRuntimeCompileWriteFile(state),
|
||||
);
|
||||
|
||||
const program = ts.createProgram({
|
||||
rootNames,
|
||||
|
@ -1590,10 +1332,9 @@ delete Object.prototype.__proto__;
|
|||
|
||||
const diagnostics = ts
|
||||
.getPreEmitDiagnostics(program)
|
||||
.filter(({ code }) => !ignoredDiagnostics.includes(code));
|
||||
.filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
|
||||
|
||||
const emitResult = program.emit();
|
||||
|
||||
assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
|
||||
|
||||
log("<<< runtime compile finish", {
|
||||
|
@ -1612,7 +1353,7 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
|
||||
function runtimeBundle(request) {
|
||||
const { options, rootNames, target, unstable, sourceFileMap } = request;
|
||||
const { compilerOptions, rootNames, target, sourceFileMap } = request;
|
||||
|
||||
log(">>> runtime bundle start", {
|
||||
rootNames,
|
||||
|
@ -1620,11 +1361,13 @@ delete Object.prototype.__proto__;
|
|||
|
||||
// if there are options, convert them into TypeScript compiler options,
|
||||
// and resolve any external file references
|
||||
let convertedOptions;
|
||||
if (options) {
|
||||
const result = convertCompilerOptions(options);
|
||||
convertedOptions = result.options;
|
||||
}
|
||||
const result = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
const options = result.options;
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
buildLocalSourceFileCache(sourceFileMap);
|
||||
|
||||
|
@ -1632,27 +1375,13 @@ delete Object.prototype.__proto__;
|
|||
rootNames,
|
||||
bundleOutput: undefined,
|
||||
};
|
||||
const host = new Host({
|
||||
bundle: true,
|
||||
target,
|
||||
writeFile: createBundleWriteFile(state),
|
||||
});
|
||||
state.host = host;
|
||||
|
||||
const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS];
|
||||
if (convertedOptions) {
|
||||
compilerOptions.push(convertedOptions);
|
||||
}
|
||||
if (unstable) {
|
||||
compilerOptions.push({
|
||||
lib: [
|
||||
"deno.unstable",
|
||||
...((convertedOptions && convertedOptions.lib) || ["deno.window"]),
|
||||
],
|
||||
});
|
||||
}
|
||||
compilerOptions.push(DEFAULT_BUNDLER_OPTIONS);
|
||||
host.mergeOptions(...compilerOptions);
|
||||
const host = new Host(
|
||||
options,
|
||||
target,
|
||||
createBundleWriteFile(state),
|
||||
);
|
||||
state.host = host;
|
||||
|
||||
const program = ts.createProgram({
|
||||
rootNames,
|
||||
|
@ -1663,7 +1392,7 @@ delete Object.prototype.__proto__;
|
|||
setRootExports(program, rootNames[0]);
|
||||
const diagnostics = ts
|
||||
.getPreEmitDiagnostics(program)
|
||||
.filter(({ code }) => !ignoredDiagnostics.includes(code));
|
||||
.filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
|
||||
|
||||
const emitResult = program.emit();
|
||||
|
||||
|
@ -1685,21 +1414,22 @@ delete Object.prototype.__proto__;
|
|||
|
||||
function runtimeTranspile(request) {
|
||||
const result = {};
|
||||
const { sources, options } = request;
|
||||
const compilerOptions = options
|
||||
? Object.assign(
|
||||
{},
|
||||
DEFAULT_RUNTIME_TRANSPILE_OPTIONS,
|
||||
convertCompilerOptions(options).options,
|
||||
)
|
||||
: DEFAULT_RUNTIME_TRANSPILE_OPTIONS;
|
||||
const { sources, compilerOptions } = request;
|
||||
|
||||
const parseResult = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
const options = parseResult.options;
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
for (const [fileName, inputText] of Object.entries(sources)) {
|
||||
const { outputText: source, sourceMapText: map } = ts.transpileModule(
|
||||
inputText,
|
||||
{
|
||||
fileName,
|
||||
compilerOptions,
|
||||
compilerOptions: options,
|
||||
},
|
||||
);
|
||||
result[fileName] = { source, map };
|
||||
|
|
236
cli/tsc_config.rs
Normal file
236
cli/tsc_config.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::ErrBox;
|
||||
use jsonc_parser::JsonValue;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct IgnoredCompilerOptions(pub Vec<String>);
|
||||
|
||||
impl fmt::Display for IgnoredCompilerOptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut codes = self.0.clone();
|
||||
codes.sort();
|
||||
write!(f, "{}", codes.join(", "))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A static slice of all the compiler options that should be ignored that
|
||||
/// either have no effect on the compilation or would cause the emit to not work
|
||||
/// in Deno.
|
||||
const IGNORED_COMPILER_OPTIONS: [&str; 61] = [
|
||||
"allowSyntheticDefaultImports",
|
||||
"allowUmdGlobalAccess",
|
||||
"assumeChangesOnlyAffectDirectDependencies",
|
||||
"baseUrl",
|
||||
"build",
|
||||
"composite",
|
||||
"declaration",
|
||||
"declarationDir",
|
||||
"declarationMap",
|
||||
"diagnostics",
|
||||
"downlevelIteration",
|
||||
"emitBOM",
|
||||
"emitDeclarationOnly",
|
||||
"esModuleInterop",
|
||||
"extendedDiagnostics",
|
||||
"forceConsistentCasingInFileNames",
|
||||
"generateCpuProfile",
|
||||
"help",
|
||||
"importHelpers",
|
||||
"incremental",
|
||||
"inlineSourceMap",
|
||||
"inlineSources",
|
||||
"init",
|
||||
"listEmittedFiles",
|
||||
"listFiles",
|
||||
"mapRoot",
|
||||
"maxNodeModuleJsDepth",
|
||||
"module",
|
||||
"moduleResolution",
|
||||
"newLine",
|
||||
"noEmit",
|
||||
"noEmitHelpers",
|
||||
"noEmitOnError",
|
||||
"noLib",
|
||||
"noResolve",
|
||||
"out",
|
||||
"outDir",
|
||||
"outFile",
|
||||
"paths",
|
||||
"preserveConstEnums",
|
||||
"preserveSymlinks",
|
||||
"preserveWatchOutput",
|
||||
"pretty",
|
||||
"reactNamespace",
|
||||
"resolveJsonModule",
|
||||
"rootDir",
|
||||
"rootDirs",
|
||||
"showConfig",
|
||||
"skipDefaultLibCheck",
|
||||
"skipLibCheck",
|
||||
"sourceMap",
|
||||
"sourceRoot",
|
||||
"stripInternal",
|
||||
"target",
|
||||
"traceResolution",
|
||||
"tsBuildInfoFile",
|
||||
"types",
|
||||
"typeRoots",
|
||||
"useDefineForClassFields",
|
||||
"version",
|
||||
"watch",
|
||||
];
|
||||
|
||||
/// A function that works like JavaScript's `Object.assign()`.
|
||||
pub fn json_merge(a: &mut Value, b: &Value) {
|
||||
match (a, b) {
|
||||
(&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
|
||||
for (k, v) in b {
|
||||
json_merge(a.entry(k.clone()).or_insert(Value::Null), v);
|
||||
}
|
||||
}
|
||||
(a, b) => {
|
||||
*a = b.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a jsonc libraries `JsonValue` to a serde `Value`.
|
||||
fn jsonc_to_serde(j: JsonValue) -> Value {
|
||||
match j {
|
||||
JsonValue::Array(arr) => {
|
||||
let vec = arr.into_iter().map(jsonc_to_serde).collect();
|
||||
Value::Array(vec)
|
||||
}
|
||||
JsonValue::Boolean(bool) => Value::Bool(bool),
|
||||
JsonValue::Null => Value::Null,
|
||||
JsonValue::Number(num) => {
|
||||
let number =
|
||||
serde_json::Number::from_str(&num).expect("could not parse number");
|
||||
Value::Number(number)
|
||||
}
|
||||
JsonValue::Object(obj) => {
|
||||
let mut map = serde_json::map::Map::new();
|
||||
for (key, json_value) in obj.into_iter() {
|
||||
map.insert(key, jsonc_to_serde(json_value));
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
JsonValue::String(str) => Value::String(str),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct TSConfigJson {
|
||||
compiler_options: Option<HashMap<String, Value>>,
|
||||
exclude: Option<Vec<String>>,
|
||||
extends: Option<String>,
|
||||
files: Option<Vec<String>>,
|
||||
include: Option<Vec<String>>,
|
||||
references: Option<Value>,
|
||||
type_acquisition: Option<Value>,
|
||||
}
|
||||
|
||||
pub fn parse_raw_config(config_text: &str) -> Result<Value, ErrBox> {
|
||||
assert!(!config_text.is_empty());
|
||||
let jsonc = jsonc_parser::parse_to_value(config_text)?.unwrap();
|
||||
Ok(jsonc_to_serde(jsonc))
|
||||
}
|
||||
|
||||
/// Take a string of JSONC, parse it and return a serde `Value` of the text.
|
||||
/// The result also contains any options that were ignored.
|
||||
pub fn parse_config(
|
||||
config_text: &str,
|
||||
) -> Result<(Value, Option<IgnoredCompilerOptions>), ErrBox> {
|
||||
assert!(!config_text.is_empty());
|
||||
let jsonc = jsonc_parser::parse_to_value(config_text)?.unwrap();
|
||||
let config: TSConfigJson = serde_json::from_value(jsonc_to_serde(jsonc))?;
|
||||
let mut compiler_options: HashMap<String, Value> = HashMap::new();
|
||||
let mut items: Vec<String> = Vec::new();
|
||||
|
||||
if let Some(in_compiler_options) = config.compiler_options {
|
||||
for (key, value) in in_compiler_options.iter() {
|
||||
if IGNORED_COMPILER_OPTIONS.contains(&key.as_str()) {
|
||||
items.push(key.to_owned());
|
||||
} else {
|
||||
compiler_options.insert(key.to_owned(), value.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
let options_value = serde_json::to_value(compiler_options)?;
|
||||
let ignored_options = if !items.is_empty() {
|
||||
Some(IgnoredCompilerOptions(items))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((options_value, ignored_options))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_json_merge() {
|
||||
let mut value_a = json!({
|
||||
"a": true,
|
||||
"b": "c"
|
||||
});
|
||||
let value_b = json!({
|
||||
"b": "d",
|
||||
"e": false,
|
||||
});
|
||||
json_merge(&mut value_a, &value_b);
|
||||
assert_eq!(
|
||||
value_a,
|
||||
json!({
|
||||
"a": true,
|
||||
"b": "d",
|
||||
"e": false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_config() {
|
||||
let config_text = r#"{
|
||||
"compilerOptions": {
|
||||
"build": true,
|
||||
// comments are allowed
|
||||
"strict": true
|
||||
}
|
||||
}"#;
|
||||
let (options_value, ignored) =
|
||||
parse_config(config_text).expect("error parsing");
|
||||
assert!(options_value.is_object());
|
||||
let options = options_value.as_object().unwrap();
|
||||
assert!(options.contains_key("strict"));
|
||||
assert_eq!(options.len(), 1);
|
||||
assert_eq!(
|
||||
ignored,
|
||||
Some(IgnoredCompilerOptions(vec!["build".to_string()])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_raw_config() {
|
||||
let invalid_config_text = r#"{
|
||||
"compilerOptions": {
|
||||
// comments are allowed
|
||||
}"#;
|
||||
let errbox = parse_raw_config(invalid_config_text).unwrap_err();
|
||||
assert!(errbox
|
||||
.to_string()
|
||||
.starts_with("Unterminated object on line 1"));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue