summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock7
-rw-r--r--cli/Cargo.toml1
-rw-r--r--cli/main.rs1
-rw-r--r--cli/tsc.rs435
-rw-r--r--cli/tsc/99_main_compiler.js478
-rw-r--r--cli/tsc_config.rs236
6 files changed, 650 insertions, 508 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f0784bc71..fe1743063 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -353,6 +353,7 @@ dependencies = [
"http",
"idna",
"indexmap",
+ "jsonc-parser",
"lazy_static",
"libc",
"log 0.4.11",
@@ -1025,6 +1026,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 7d81f12f6..e0f75da15 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -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"
diff --git a/cli/main.rs b/cli/main.rs
index b19465713..d6b74d8a2 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -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;
diff --git a/cli/tsc.rs b/cli/tsc.rs
index d509d99ce..cc902d196 100644
--- a/cli/tsc.rs
+++ b/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,39 +585,57 @@ 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 },
- }),
+ // 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"]
};
- let req_msg = j.to_string();
+ if unstable {
+ lib.push("deno.unstable");
+ }
- // TODO(bartlomieju): lift this call up - TSC shouldn't print anything
- info!("{} {}", colors::green("Check"), module_url.to_string());
+ 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),
];
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index 1bef2cf65..79af46e31 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -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,
+ );
+
+ diagnostics = diags.filter(
+ ({ code }) => code != 5023 && !IGNORED_DIAGNOSTICS.includes(code),
+ );
- // 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) || [];
- }
+ // 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,
+
+ 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,
- unstable,
- writeFile: createBundleWriteFile(state),
- });
+ 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) || [];
- }
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,28 +1375,14 @@ delete Object.prototype.__proto__;
rootNames,
bundleOutput: undefined,
};
- const host = new Host({
- bundle: true,
+
+ const host = new Host(
+ options,
target,
- writeFile: createBundleWriteFile(state),
- });
+ 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 program = ts.createProgram({
rootNames,
options: host.getCompilationSettings(),
@@ -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 };
diff --git a/cli/tsc_config.rs b/cli/tsc_config.rs
new file mode 100644
index 000000000..e5f7bcdc4
--- /dev/null
+++ b/cli/tsc_config.rs
@@ -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"));
+ }
+}