diff --git a/cli/ast.rs b/cli/ast.rs index f76bf5e9a0..d282e0ee72 100644 --- a/cli/ast.rs +++ b/cli/ast.rs @@ -1,7 +1,7 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use crate::config_file; use crate::media_type::MediaType; -use crate::tsc_config; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; @@ -229,9 +229,9 @@ impl Default for EmitOptions { } } -impl From for EmitOptions { - fn from(config: tsc_config::TsConfig) -> Self { - let options: tsc_config::EmitConfigOptions = +impl From for EmitOptions { + fn from(config: config_file::TsConfig) -> Self { + let options: config_file::EmitConfigOptions = serde_json::from_value(config.0).unwrap(); let imports_not_used_as_values = match options.imports_not_used_as_values.as_str() { diff --git a/cli/tsc_config.rs b/cli/config_file.rs similarity index 79% rename from cli/tsc_config.rs rename to cli/config_file.rs index ab4bbe7120..fcc702ad58 100644 --- a/cli/tsc_config.rs +++ b/cli/config_file.rs @@ -2,6 +2,7 @@ use crate::fs_util::canonicalize_path; use deno_core::error::AnyError; +use deno_core::error::Context; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; use deno_core::serde::Serializer; @@ -147,19 +148,6 @@ pub fn json_merge(a: &mut Value, b: &Value) { } } -/// A structure for deserializing a `tsconfig.json` file for the purposes of -/// being used internally within Deno. -/// -/// The only key in the JSON object that Deno cares about is the -/// `compilerOptions` property. A valid `tsconfig.json` file can also contain -/// the keys `exclude`, `extends`, `files`, `include`, `references`, and -/// `typeAcquisition` which are all "ignored" by Deno. -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TsConfigJson { - compiler_options: Option>, -} - fn parse_compiler_options( compiler_options: &HashMap, maybe_path: Option, @@ -188,23 +176,6 @@ fn parse_compiler_options( Ok((value, maybe_ignored_options)) } -/// 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, - path: &Path, -) -> Result<(Value, Option), AnyError> { - assert!(!config_text.is_empty()); - let jsonc = jsonc_parser::parse_to_serde_value(config_text)?.unwrap(); - let config: TsConfigJson = serde_json::from_value(jsonc)?; - - if let Some(compiler_options) = config.compiler_options { - parse_compiler_options(&compiler_options, Some(path.to_owned()), false) - } else { - Ok((json!({}), None)) - } -} - /// A structure for managing the configuration of TypeScript #[derive(Debug, Clone)] pub struct TsConfig(pub Value); @@ -245,34 +216,17 @@ impl TsConfig { json_merge(&mut self.0, value); } - /// Take an optional string representing a user provided TypeScript config file - /// which was passed in via the `--config` compiler option and merge it with + /// Take an optional user provided config file + /// which was passed in via the `--config` flag and merge `compilerOptions` with /// the configuration. Returning the result which optionally contains any /// compiler options that were ignored. - /// - /// When there are options ignored out of the file, a warning will be written - /// to stderr regarding the options that were ignored. - pub fn merge_tsconfig( + pub fn merge_tsconfig_from_config_file( &mut self, - maybe_path: Option, + maybe_config_file: Option<&ConfigFile>, ) -> Result, AnyError> { - if let Some(path) = maybe_path { - let cwd = std::env::current_dir()?; - let config_file = cwd.join(path); - let config_path = canonicalize_path(&config_file).map_err(|_| { - std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!( - "Could not find the config file: {}", - config_file.to_string_lossy() - ), - ) - })?; - let config_text = std::fs::read_to_string(config_path.clone())?; - let (value, maybe_ignored_options) = - parse_config(&config_text, &config_path)?; - json_merge(&mut self.0, &value); - + if let Some(config_file) = maybe_config_file { + let (value, maybe_ignored_options) = config_file.as_compiler_options()?; + self.merge(&value); Ok(maybe_ignored_options) } else { Ok(None) @@ -288,7 +242,7 @@ impl TsConfig { ) -> Result, AnyError> { let (value, maybe_ignored_options) = parse_compiler_options(user_options, None, true)?; - json_merge(&mut self.0, &value); + self.merge(&value); Ok(maybe_ignored_options) } } @@ -303,11 +257,73 @@ impl Serialize for TsConfig { } } +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigFileJson { + pub compiler_options: Option, +} + +#[derive(Clone, Debug)] +pub struct ConfigFile { + pub path: PathBuf, + pub json: ConfigFileJson, +} + +impl ConfigFile { + pub fn read(path: &str) -> Result { + let cwd = std::env::current_dir()?; + let config_file = cwd.join(path); + let config_path = canonicalize_path(&config_file).map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!( + "Could not find the config file: {}", + config_file.to_string_lossy() + ), + ) + })?; + let config_text = std::fs::read_to_string(config_path.clone())?; + Self::new(&config_text, &config_path) + } + + pub fn new(text: &str, path: &Path) -> Result { + let jsonc = jsonc_parser::parse_to_serde_value(text)?.unwrap(); + let json: ConfigFileJson = serde_json::from_value(jsonc)?; + + Ok(Self { + path: path.to_owned(), + json, + }) + } + + /// Parse `compilerOptions` and return a serde `Value`. + /// The result also contains any options that were ignored. + pub fn as_compiler_options( + &self, + ) -> Result<(Value, Option), AnyError> { + if let Some(compiler_options) = self.json.compiler_options.clone() { + let options: HashMap = + serde_json::from_value(compiler_options) + .context("compilerOptions should be an object")?; + parse_compiler_options(&options, Some(self.path.to_owned()), false) + } else { + Ok((json!({}), None)) + } + } +} + #[cfg(test)] mod tests { use super::*; use deno_core::serde_json::json; + #[test] + fn read_config_file() { + let config_file = ConfigFile::read("tests/module_graph/tsconfig.json") + .expect("Failed to load config file"); + assert!(config_file.json.compiler_options.is_some()); + } + #[test] fn test_json_merge() { let mut value_a = json!({ @@ -339,8 +355,9 @@ mod tests { } }"#; let config_path = PathBuf::from("/deno/tsconfig.json"); + let config_file = ConfigFile::new(config_text, &config_path).unwrap(); let (options_value, ignored) = - parse_config(config_text, &config_path).expect("error parsing"); + config_file.as_compiler_options().expect("error parsing"); assert!(options_value.is_object()); let options = options_value.as_object().unwrap(); assert!(options.contains_key("strict")); diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 7aca52a9b6..8cf4a67ef4 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -102,7 +102,7 @@ pub struct WorkspaceSettings { /// A flag that indicates if Deno is enabled for the workspace. pub enable: bool, - /// An option that points to a path string of the tsconfig file to apply to + /// An option that points to a path string of the config file to apply to /// code within the workspace. pub config: Option, diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 2fbc41b034..c14617e5ae 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -2,6 +2,7 @@ use deno_core::error::anyhow; use deno_core::error::AnyError; +use deno_core::error::Context; use deno_core::resolve_url; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; @@ -28,11 +29,11 @@ use std::rc::Rc; use std::sync::Arc; use tokio::fs; +use crate::config_file::ConfigFile; +use crate::config_file::TsConfig; use crate::deno_dir; use crate::import_map::ImportMap; use crate::media_type::MediaType; -use crate::tsc_config::parse_config; -use crate::tsc_config::TsConfig; use super::analysis; use super::analysis::ts_changes_to_edit; @@ -408,21 +409,10 @@ impl Inner { config_str )) }?; - let config_path = config_url - .to_file_path() - .map_err(|_| anyhow!("Bad file path."))?; - let config_text = - fs::read_to_string(config_path.clone()) - .await - .map_err(|err| { - anyhow!( - "Failed to load the configuration file at: {}. [{}]", - config_url, - err - ) - })?; - let (value, maybe_ignored_options) = - parse_config(&config_text, &config_path)?; + + let config_file = ConfigFile::read(config_url.as_str()) + .context("Failed to load configuration file")?; + let (value, maybe_ignored_options) = config_file.as_compiler_options()?; tsconfig.merge(&value); self.maybe_config_uri = Some(config_url); if let Some(ignored_options) = maybe_ignored_options { diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 869a61838f..d5cedfd515 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -11,11 +11,11 @@ use super::semantic_tokens::TsTokenEncodingConsts; use super::text; use super::text::LineIndex; +use crate::config_file::TsConfig; use crate::media_type::MediaType; use crate::tokio_util::create_basic_runtime; use crate::tsc; use crate::tsc::ResolveArgs; -use crate::tsc_config::TsConfig; use deno_core::error::anyhow; use deno_core::error::custom_error; diff --git a/cli/main.rs b/cli/main.rs index 43c1cd0b2e..35a0fed6ec 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -4,6 +4,7 @@ mod ast; mod auth_tokens; mod checksum; mod colors; +mod config_file; mod deno_dir; mod diagnostics; mod diff; @@ -33,7 +34,6 @@ mod text_encoding; mod tokio_util; mod tools; mod tsc; -mod tsc_config; mod unix_util; mod version; @@ -359,7 +359,8 @@ async fn compile_command( colors::green("Bundle"), module_specifier.to_string() ); - let bundle_str = bundle_module_graph(module_graph, flags, debug)?; + let bundle_str = + bundle_module_graph(module_graph, program_state.clone(), flags, debug)?; info!( "{} {}", @@ -565,7 +566,7 @@ async fn create_module_graph_and_maybe_check( debug, emit: false, lib, - maybe_config_path: program_state.flags.config_path.clone(), + maybe_config_file: program_state.maybe_config_file.clone(), reload: program_state.flags.reload, })?; @@ -583,13 +584,14 @@ async fn create_module_graph_and_maybe_check( fn bundle_module_graph( module_graph: module_graph::Graph, + program_state: Arc, flags: Flags, debug: bool, ) -> Result { let (bundle, stats, maybe_ignored_options) = module_graph.bundle(module_graph::BundleOptions { debug, - maybe_config_path: flags.config_path, + maybe_config_file: program_state.maybe_config_file.clone(), })?; match maybe_ignored_options { Some(ignored_options) if flags.no_check => { @@ -636,13 +638,15 @@ async fn bundle_command( .push(fs_util::resolve_from_cwd(std::path::Path::new(import_map))?); } - Ok((paths_to_watch, module_graph)) + Ok((paths_to_watch, module_graph, program_state)) } .map(move |result| match result { - Ok((paths_to_watch, module_graph)) => ResolutionResult::Restart { - paths_to_watch, - result: Ok(module_graph), - }, + Ok((paths_to_watch, module_graph, program_state)) => { + ResolutionResult::Restart { + paths_to_watch, + result: Ok((program_state, module_graph)), + } + } Err(e) => ResolutionResult::Restart { paths_to_watch: vec![PathBuf::from(source_file2)], result: Err(e), @@ -650,13 +654,17 @@ async fn bundle_command( }) }; - let operation = |module_graph: module_graph::Graph| { + let operation = |(program_state, module_graph): ( + Arc, + module_graph::Graph, + )| { let flags = flags.clone(); let out_file = out_file.clone(); async move { info!("{} {}", colors::green("Bundle"), module_graph.info()?.root); - let output = bundle_module_graph(module_graph, flags, debug)?; + let output = + bundle_module_graph(module_graph, program_state, flags, debug)?; debug!(">>>>> bundle END"); @@ -794,13 +802,15 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { .push(fs_util::resolve_from_cwd(std::path::Path::new(import_map))?); } - Ok((paths_to_watch, main_module)) + Ok((paths_to_watch, main_module, program_state)) } .map(move |result| match result { - Ok((paths_to_watch, module_info)) => ResolutionResult::Restart { - paths_to_watch, - result: Ok(module_info), - }, + Ok((paths_to_watch, module_info, program_state)) => { + ResolutionResult::Restart { + paths_to_watch, + result: Ok((program_state, module_info)), + } + } Err(e) => ResolutionResult::Restart { paths_to_watch: vec![PathBuf::from(script2)], result: Err(e), @@ -808,26 +818,26 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { }) }; - let operation = |main_module: ModuleSpecifier| { - let flags = flags.clone(); - let permissions = Permissions::from_options(&flags.clone().into()); - async move { - let main_module = main_module.clone(); - let program_state = ProgramState::build(flags).await?; - let mut worker = create_main_worker( - &program_state, - main_module.clone(), - permissions, - false, - ); - debug!("main_module {}", main_module); - worker.execute_module(&main_module).await?; - worker.execute("window.dispatchEvent(new Event('load'))")?; - worker.run_event_loop().await?; - worker.execute("window.dispatchEvent(new Event('unload'))")?; - Ok(()) - } - }; + let operation = + |(program_state, main_module): (Arc, ModuleSpecifier)| { + let flags = flags.clone(); + let permissions = Permissions::from_options(&flags.into()); + async move { + let main_module = main_module.clone(); + let mut worker = create_main_worker( + &program_state, + main_module.clone(), + permissions, + false, + ); + debug!("main_module {}", main_module); + worker.execute_module(&main_module).await?; + worker.execute("window.dispatchEvent(new Event('load'))")?; + worker.run_event_loop().await?; + worker.execute("window.dispatchEvent(new Event('unload'))")?; + Ok(()) + } + }; file_watcher::watch_func(resolver, operation, "Process").await } diff --git a/cli/module_graph.rs b/cli/module_graph.rs index 2300e89d69..a70a124a77 100644 --- a/cli/module_graph.rs +++ b/cli/module_graph.rs @@ -7,6 +7,9 @@ use crate::ast::Location; use crate::ast::ParsedModule; use crate::checksum; use crate::colors; +use crate::config_file::ConfigFile; +use crate::config_file::IgnoredCompilerOptions; +use crate::config_file::TsConfig; use crate::diagnostics::Diagnostics; use crate::import_map::ImportMap; use crate::info; @@ -19,8 +22,6 @@ use crate::specifier_handler::Emit; use crate::specifier_handler::FetchFuture; use crate::specifier_handler::SpecifierHandler; use crate::tsc; -use crate::tsc_config::IgnoredCompilerOptions; -use crate::tsc_config::TsConfig; use crate::version; use deno_core::error::anyhow; use deno_core::error::custom_error; @@ -579,10 +580,10 @@ impl Serialize for TypeLib { pub struct BundleOptions { /// If `true` then debug logging will be output from the isolate. pub debug: bool, - /// An optional string that points to a user supplied TypeScript configuration - /// file that augments the the default configuration passed to the TypeScript + /// An optional config file with user supplied TypeScript configuration + /// that augments the the default configuration passed to the TypeScript /// compiler. - pub maybe_config_path: Option, + pub maybe_config_file: Option, } #[derive(Debug, Default)] @@ -593,10 +594,10 @@ pub struct CheckOptions { pub emit: bool, /// The base type libraries that should be used when type checking. pub lib: TypeLib, - /// An optional string that points to a user supplied TypeScript configuration - /// file that augments the the default configuration passed to the TypeScript + /// An optional config file with user supplied TypeScript configuration + /// that augments the the default configuration passed to the TypeScript /// compiler. - pub maybe_config_path: Option, + pub maybe_config_file: Option, /// Ignore any previously emits and ensure that all files are emitted from /// source. pub reload: bool, @@ -642,10 +643,10 @@ pub struct EmitOptions { pub struct TranspileOptions { /// If `true` then debug logging will be output from the isolate. pub debug: bool, - /// An optional string that points to a user supplied TypeScript configuration - /// file that augments the the default configuration passed to the TypeScript + /// An optional config file with user supplied TypeScript configuration + /// that augments the the default configuration passed to the TypeScript /// compiler. - pub maybe_config_path: Option, + pub maybe_config_file: Option, /// Ignore any previously emits and ensure that all files are emitted from /// source. pub reload: bool, @@ -773,8 +774,8 @@ impl Graph { "jsxFactory": "React.createElement", "jsxFragmentFactory": "React.Fragment", })); - let maybe_ignored_options = - ts_config.merge_tsconfig(options.maybe_config_path)?; + let maybe_ignored_options = ts_config + .merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?; let s = self.emit_bundle( &root_specifier, @@ -823,8 +824,8 @@ impl Graph { "noEmit": true, })); } - let maybe_ignored_options = - config.merge_tsconfig(options.maybe_config_path)?; + let maybe_ignored_options = config + .merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?; // Short circuit if none of the modules require an emit, or all of the // modules that require an emit have a valid emit. There is also an edge @@ -1610,8 +1611,8 @@ impl Graph { "jsxFragmentFactory": "React.Fragment", })); - let maybe_ignored_options = - ts_config.merge_tsconfig(options.maybe_config_path)?; + let maybe_ignored_options = ts_config + .merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?; let config = ts_config.as_bytes(); let emit_options: ast::EmitOptions = ts_config.into(); @@ -2178,7 +2179,7 @@ pub mod tests { debug: false, emit: true, lib: TypeLib::DenoWindow, - maybe_config_path: None, + maybe_config_file: None, reload: false, }) .expect("should have checked"); @@ -2200,7 +2201,7 @@ pub mod tests { debug: false, emit: false, lib: TypeLib::DenoWindow, - maybe_config_path: None, + maybe_config_file: None, reload: false, }) .expect("should have checked"); @@ -2217,7 +2218,7 @@ pub mod tests { debug: false, emit: true, lib: TypeLib::DenoWindow, - maybe_config_path: None, + maybe_config_file: None, reload: false, }) .expect("should have checked"); @@ -2241,7 +2242,7 @@ pub mod tests { debug: false, emit: false, lib: TypeLib::DenoWindow, - maybe_config_path: None, + maybe_config_file: None, reload: false, }) .expect("should have checked"); @@ -2263,7 +2264,7 @@ pub mod tests { debug: false, emit: true, lib: TypeLib::DenoWindow, - maybe_config_path: None, + maybe_config_file: None, reload: false, }) .expect("should have checked"); @@ -2284,7 +2285,7 @@ pub mod tests { debug: false, emit: false, lib: TypeLib::DenoWindow, - maybe_config_path: None, + maybe_config_file: None, reload: false, }) .expect("should have checked"); @@ -2296,14 +2297,14 @@ pub mod tests { let specifier = resolve_url_or_path("file:///tests/checkwithconfig.ts") .expect("could not resolve module"); let (graph, handler) = setup(specifier.clone()).await; + let config_file = + ConfigFile::read("tests/module_graph/tsconfig_01.json").unwrap(); let result_info = graph .check(CheckOptions { debug: false, emit: true, lib: TypeLib::DenoWindow, - maybe_config_path: Some( - "tests/module_graph/tsconfig_01.json".to_string(), - ), + maybe_config_file: Some(config_file), reload: true, }) .expect("should have checked"); @@ -2317,14 +2318,14 @@ pub mod tests { // let's do it all over again to ensure that the versions are determinstic let (graph, handler) = setup(specifier).await; + let config_file = + ConfigFile::read("tests/module_graph/tsconfig_01.json").unwrap(); let result_info = graph .check(CheckOptions { debug: false, emit: true, lib: TypeLib::DenoWindow, - maybe_config_path: Some( - "tests/module_graph/tsconfig_01.json".to_string(), - ), + maybe_config_file: Some(config_file), reload: true, }) .expect("should have checked"); @@ -2545,10 +2546,12 @@ pub mod tests { let specifier = resolve_url_or_path("https://deno.land/x/transpile.tsx") .expect("could not resolve module"); let (mut graph, handler) = setup(specifier).await; + let config_file = + ConfigFile::read("tests/module_graph/tsconfig.json").unwrap(); let result_info = graph .transpile(TranspileOptions { debug: false, - maybe_config_path: Some("tests/module_graph/tsconfig.json".to_string()), + maybe_config_file: Some(config_file), reload: false, }) .unwrap(); diff --git a/cli/program_state.rs b/cli/program_state.rs index e8d2c163e5..0051e744b3 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -1,5 +1,6 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use crate::config_file::ConfigFile; use crate::deno_dir; use crate::file_fetcher::CacheSetting; use crate::file_fetcher::FileFetcher; @@ -46,6 +47,7 @@ pub struct ProgramState { pub modules: Arc>>>, pub lockfile: Option>>, + pub maybe_config_file: Option, pub maybe_import_map: Option, pub maybe_inspector_server: Option>, pub ca_data: Option>, @@ -91,6 +93,13 @@ impl ProgramState { None }; + let maybe_config_file = + if let Some(config_path) = flags.config_path.as_ref() { + Some(ConfigFile::read(config_path)?) + } else { + None + }; + let maybe_import_map: Option = match flags.import_map_path.as_ref() { None => None, @@ -129,6 +138,7 @@ impl ProgramState { file_fetcher, modules: Default::default(), lockfile, + maybe_config_file, maybe_import_map, maybe_inspector_server, ca_data, @@ -160,12 +170,12 @@ impl ProgramState { let mut graph = builder.get_graph(); let debug = self.flags.log_level == Some(log::Level::Debug); - let maybe_config_path = self.flags.config_path.clone(); + let maybe_config_file = self.maybe_config_file.clone(); let result_modules = if self.flags.no_check { let result_info = graph.transpile(TranspileOptions { debug, - maybe_config_path, + maybe_config_file, reload: self.flags.reload, })?; debug!("{}", result_info.stats); @@ -178,7 +188,7 @@ impl ProgramState { debug, emit: true, lib, - maybe_config_path, + maybe_config_file, reload: self.flags.reload, })?; @@ -229,12 +239,12 @@ impl ProgramState { builder.add(&specifier, is_dynamic).await?; let mut graph = builder.get_graph(); let debug = self.flags.log_level == Some(log::Level::Debug); - let maybe_config_path = self.flags.config_path.clone(); + let maybe_config_file = self.maybe_config_file.clone(); let result_modules = if self.flags.no_check { let result_info = graph.transpile(TranspileOptions { debug, - maybe_config_path, + maybe_config_file, reload: self.flags.reload, })?; debug!("{}", result_info.stats); @@ -247,7 +257,7 @@ impl ProgramState { debug, emit: true, lib, - maybe_config_path, + maybe_config_file, reload: self.flags.reload, })?; diff --git a/cli/tsc.rs b/cli/tsc.rs index 7abae5ca1a..a47cc1ab0a 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -1,10 +1,10 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use crate::config_file::TsConfig; use crate::diagnostics::Diagnostics; use crate::media_type::MediaType; use crate::module_graph::Graph; use crate::module_graph::Stats; -use crate::tsc_config::TsConfig; use deno_core::error::anyhow; use deno_core::error::bail; @@ -538,11 +538,11 @@ pub fn exec(request: Request) -> Result { #[cfg(test)] mod tests { use super::*; + use crate::config_file::TsConfig; use crate::diagnostics::Diagnostic; use crate::diagnostics::DiagnosticCategory; use crate::module_graph::tests::MockSpecifierHandler; use crate::module_graph::GraphBuilder; - use crate::tsc_config::TsConfig; use std::env; use std::path::PathBuf; use std::sync::Mutex;