diff --git a/cli/emit.rs b/cli/emit.rs index eb8a56ad03..f2d890adc5 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -8,25 +8,14 @@ use crate::args::config_file::IgnoredCompilerOptions; use crate::args::ConfigFile; use crate::args::EmitConfigOptions; use crate::args::TsConfig; -use crate::args::TypeCheckMode; use crate::cache::EmitCache; use crate::cache::FastInsecureHasher; use crate::cache::ParsedSourceCache; -use crate::cache::TypeCheckCache; -use crate::colors; -use crate::diagnostics::Diagnostics; -use crate::graph_util::GraphData; -use crate::graph_util::ModuleEntry; -use crate::tsc; -use crate::version; use deno_ast::swc::bundler::Hook; use deno_ast::swc::bundler::ModuleRecord; use deno_ast::swc::common::Span; use deno_core::error::AnyError; -use deno_core::parking_lot::RwLock; -use deno_core::serde::Deserialize; -use deno_core::serde::Deserializer; use deno_core::serde::Serialize; use deno_core::serde::Serializer; use deno_core::serde_json; @@ -34,47 +23,10 @@ use deno_core::serde_json::json; use deno_core::ModuleSpecifier; use deno_graph::MediaType; use deno_graph::ModuleGraphError; -use deno_graph::ModuleKind; use deno_graph::ResolutionError; -use once_cell::sync::Lazy; -use regex::Regex; use std::fmt; use std::sync::Arc; -/// A structure representing stats from an emit operation for a graph. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Stats(pub Vec<(String, u32)>); - -impl<'de> Deserialize<'de> for Stats { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?; - Ok(Stats(items)) - } -} - -impl Serialize for Stats { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - Serialize::serialize(&self.0, serializer) - } -} - -impl fmt::Display for Stats { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "Compilation statistics:")?; - for (key, value) in self.0.clone() { - writeln!(f, " {}: {}", key, value)?; - } - - Ok(()) - } -} - /// Represents the "default" type library that should be used when type /// checking the code in the module graph. Note that a user provided config /// of `"lib"` would override this value. @@ -193,40 +145,6 @@ pub fn get_ts_config_for_emit( }) } -/// Transform the graph into root specifiers that we can feed `tsc`. We have to -/// provide the media type for root modules because `tsc` does not "resolve" the -/// media type like other modules, as well as a root specifier needs any -/// redirects resolved. We need to include all the emittable files in -/// the roots, so they get type checked and optionally emitted, -/// otherwise they would be ignored if only imported into JavaScript. -fn get_tsc_roots( - graph_data: &GraphData, - check_js: bool, -) -> Vec<(ModuleSpecifier, MediaType)> { - graph_data - .entries() - .into_iter() - .filter_map(|(specifier, module_entry)| match module_entry { - ModuleEntry::Module { - media_type, code, .. - } => match media_type { - MediaType::TypeScript - | MediaType::Tsx - | MediaType::Mts - | MediaType::Cts - | MediaType::Jsx => Some((specifier.clone(), *media_type)), - MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs - if check_js || has_ts_check(*media_type, code) => - { - Some((specifier.clone(), *media_type)) - } - _ => None, - }, - _ => None, - }) - .collect() -} - /// A hashing function that takes the source code, version and optionally a /// user provided config and generates a string hash which can be stored to /// determine if the cached emit is valid or not. @@ -266,193 +184,6 @@ pub fn emit_parsed_source( } } -/// Options for performing a check of a module graph. Note that the decision to -/// emit or not is determined by the `ts_config` settings. -pub struct CheckOptions { - /// The check flag from the option which can effect the filtering of - /// diagnostics in the emit result. - pub type_check_mode: TypeCheckMode, - /// Set the debug flag on the TypeScript type checker. - pub debug: bool, - /// The module specifier to the configuration file, passed to tsc so that - /// configuration related diagnostics are properly formed. - pub maybe_config_specifier: Option, - /// The derived tsconfig that should be used when checking. - pub ts_config: TsConfig, - /// If true, `Check ` will be written to stdout for each root. - pub log_checks: bool, - /// If true, valid `.tsbuildinfo` files will be ignored and type checking - /// will always occur. - pub reload: bool, -} - -/// The result of a check of a module graph. -#[derive(Debug, Default)] -pub struct CheckResult { - pub diagnostics: Diagnostics, - pub stats: Stats, -} - -/// Given a set of roots and graph data, type check the module graph. -/// -/// It is expected that it is determined if a check and/or emit is validated -/// before the function is called. -pub fn check( - roots: &[(ModuleSpecifier, ModuleKind)], - graph_data: Arc>, - cache: &TypeCheckCache, - options: CheckOptions, -) -> Result { - let check_js = options.ts_config.get_check_js(); - let segment_graph_data = { - let graph_data = graph_data.read(); - graph_data.graph_segment(roots).unwrap() - }; - let check_hash = match get_check_hash(&segment_graph_data, &options) { - CheckHashResult::NoFiles => return Ok(Default::default()), - CheckHashResult::Hash(hash) => hash, - }; - - // do not type check if we know this is type checked - if !options.reload && cache.has_check_hash(check_hash) { - return Ok(Default::default()); - } - - let root_names = get_tsc_roots(&segment_graph_data, check_js); - if options.log_checks { - for (root, _) in roots { - let root_str = root.to_string(); - // `$deno` specifiers are internal, don't print them. - if !root_str.contains("$deno") { - log::info!("{} {}", colors::green("Check"), root); - } - } - } - // while there might be multiple roots, we can't "merge" the build info, so we - // try to retrieve the build info for first root, which is the most common use - // case. - let maybe_tsbuildinfo = if options.reload { - None - } else { - cache.get_tsbuildinfo(&roots[0].0) - }; - // to make tsc build info work, we need to consistently hash modules, so that - // tsc can better determine if an emit is still valid or not, so we provide - // that data here. - let hash_data = vec![ - options.ts_config.as_bytes(), - version::deno().as_bytes().to_owned(), - ]; - - let response = tsc::exec(tsc::Request { - config: options.ts_config, - debug: options.debug, - graph_data, - hash_data, - maybe_config_specifier: options.maybe_config_specifier, - maybe_tsbuildinfo, - root_names, - })?; - - let diagnostics = if options.type_check_mode == TypeCheckMode::Local { - response.diagnostics.filter(|d| { - if let Some(file_name) = &d.file_name { - !file_name.starts_with("http") - } else { - true - } - }) - } else { - response.diagnostics - }; - - if let Some(tsbuildinfo) = response.maybe_tsbuildinfo { - cache.set_tsbuildinfo(&roots[0].0, &tsbuildinfo); - } - - if diagnostics.is_empty() { - cache.add_check_hash(check_hash); - } - - Ok(CheckResult { - diagnostics, - stats: response.stats, - }) -} - -enum CheckHashResult { - Hash(u64), - NoFiles, -} - -/// Gets a hash of the inputs for type checking. This can then -/// be used to tell -fn get_check_hash( - graph_data: &GraphData, - options: &CheckOptions, -) -> CheckHashResult { - let mut hasher = FastInsecureHasher::new(); - hasher.write_u8(match options.type_check_mode { - TypeCheckMode::All => 0, - TypeCheckMode::Local => 1, - TypeCheckMode::None => 2, - }); - hasher.write(&options.ts_config.as_bytes()); - - let check_js = options.ts_config.get_check_js(); - let mut sorted_entries = graph_data.entries().collect::>(); - sorted_entries.sort_by_key(|(s, _)| s.as_str()); // make it deterministic - let mut has_file = false; - let mut has_file_to_type_check = false; - for (specifier, module_entry) in sorted_entries { - if let ModuleEntry::Module { - code, media_type, .. - } = module_entry - { - let ts_check = has_ts_check(*media_type, code); - if ts_check { - has_file_to_type_check = true; - } - - match media_type { - MediaType::TypeScript - | MediaType::Dts - | MediaType::Dmts - | MediaType::Dcts - | MediaType::Mts - | MediaType::Cts - | MediaType::Tsx => { - has_file = true; - has_file_to_type_check = true; - } - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx => { - has_file = true; - if !check_js && !ts_check { - continue; - } - } - MediaType::Json - | MediaType::TsBuildInfo - | MediaType::SourceMap - | MediaType::Wasm - | MediaType::Unknown => continue, - } - hasher.write_str(specifier.as_str()); - hasher.write_str(code); - } - } - - if !has_file || !check_js && !has_file_to_type_check { - // no files to type check - CheckHashResult::NoFiles - } else { - CheckHashResult::Hash(hasher.finish()) - } -} - /// An adapter struct to make a deno_graph::ModuleGraphError display as expected /// in the Deno CLI. #[derive(Debug)] @@ -556,128 +287,3 @@ impl From for deno_ast::EmitOptions { } } } - -/// Matches the `@ts-check` pragma. -static TS_CHECK_RE: Lazy = - Lazy::new(|| Regex::new(r#"(?i)^\s*@ts-check(?:\s+|$)"#).unwrap()); - -fn has_ts_check(media_type: MediaType, file_text: &str) -> bool { - match &media_type { - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx => get_leading_comments(file_text) - .iter() - .any(|text| TS_CHECK_RE.is_match(text)), - _ => false, - } -} - -fn get_leading_comments(file_text: &str) -> Vec { - let mut chars = file_text.chars().peekable(); - - // skip over the shebang - if file_text.starts_with("#!") { - // skip until the end of the line - for c in chars.by_ref() { - if c == '\n' { - break; - } - } - } - - let mut results = Vec::new(); - // now handle the comments - while chars.peek().is_some() { - // skip over any whitespace - while chars - .peek() - .map(|c| char::is_whitespace(*c)) - .unwrap_or(false) - { - chars.next(); - } - - if chars.next() != Some('/') { - break; - } - match chars.next() { - Some('/') => { - let mut text = String::new(); - for c in chars.by_ref() { - if c == '\n' { - break; - } else { - text.push(c); - } - } - results.push(text); - } - Some('*') => { - let mut text = String::new(); - while let Some(c) = chars.next() { - if c == '*' && chars.peek() == Some(&'/') { - chars.next(); - break; - } else { - text.push(c); - } - } - results.push(text); - } - _ => break, - } - } - results -} - -#[cfg(test)] -mod test { - use deno_ast::MediaType; - - use super::get_leading_comments; - use super::has_ts_check; - - #[test] - fn get_leading_comments_test() { - assert_eq!( - get_leading_comments( - "#!/usr/bin/env deno\r\n// test\n/* 1 *//*2*///3\n//\n /**/ /*4 */" - ), - vec![ - " test".to_string(), - " 1 ".to_string(), - "2".to_string(), - "3".to_string(), - "".to_string(), - "".to_string(), - "4 ".to_string(), - ] - ); - assert_eq!( - get_leading_comments("//1 /* */ \na;"), - vec!["1 /* */ ".to_string(),] - ); - assert_eq!(get_leading_comments("//"), vec!["".to_string()]); - } - - #[test] - fn has_ts_check_test() { - assert!(has_ts_check( - MediaType::JavaScript, - "// @ts-check\nconsole.log(5);" - )); - assert!(has_ts_check( - MediaType::JavaScript, - "// deno-lint-ignore\n// @ts-check\n" - )); - assert!(!has_ts_check( - MediaType::JavaScript, - "test;\n// @ts-check\n" - )); - assert!(!has_ts_check( - MediaType::JavaScript, - "// ts-check\nconsole.log(5);" - )); - } -} diff --git a/cli/main.rs b/cli/main.rs index 391b4ff0d5..0c4b5c8935 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -73,6 +73,7 @@ use crate::graph_util::graph_valid; use crate::proc_state::ProcState; use crate::resolver::ImportMapResolver; use crate::resolver::JsxResolver; +use crate::tools::check; use args::CliOptions; use deno_ast::MediaType; @@ -500,11 +501,11 @@ async fn create_graph_and_maybe_check( } let maybe_config_specifier = ps.options.maybe_config_file_specifier(); let cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path()); - let check_result = emit::check( + let check_result = check::check( &graph.roots, Arc::new(RwLock::new(graph.as_ref().into())), &cache, - emit::CheckOptions { + check::CheckOptions { type_check_mode: ps.options.type_check_mode(), debug, maybe_config_specifier, diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 42c34686f7..7d29cd89fd 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -12,7 +12,6 @@ use crate::cache::TypeCheckCache; use crate::compat; use crate::compat::NodeEsmResolver; use crate::deno_dir; -use crate::emit; use crate::emit::emit_parsed_source; use crate::emit::TsConfigType; use crate::emit::TsTypeLib; @@ -29,6 +28,7 @@ use crate::npm::NpmPackageReference; use crate::npm::NpmPackageResolver; use crate::resolver::ImportMapResolver; use crate::resolver::JsxResolver; +use crate::tools::check; use deno_ast::MediaType; use deno_core::anyhow::anyhow; @@ -453,7 +453,7 @@ impl ProcState { if self.options.type_check_mode() != TypeCheckMode::None { let maybe_config_specifier = self.options.maybe_config_file_specifier(); let roots = roots.clone(); - let options = emit::CheckOptions { + let options = check::CheckOptions { type_check_mode: self.options.type_check_mode(), debug: self.options.log_level() == Some(log::Level::Debug), maybe_config_specifier, @@ -469,7 +469,7 @@ impl ProcState { TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path()); let graph_data = self.graph_data.clone(); let check_result = - emit::check(&roots, graph_data, &check_cache, options)?; + check::check(&roots, graph_data, &check_cache, options)?; if !check_result.diagnostics.is_empty() { return Err(anyhow!(check_result.diagnostics)); } diff --git a/cli/tools/check.rs b/cli/tools/check.rs new file mode 100644 index 0000000000..bb0b873f45 --- /dev/null +++ b/cli/tools/check.rs @@ -0,0 +1,369 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::sync::Arc; + +use deno_ast::MediaType; +use deno_ast::ModuleSpecifier; +use deno_core::error::AnyError; +use deno_core::parking_lot::RwLock; +use deno_graph::ModuleKind; +use deno_runtime::colors; +use once_cell::sync::Lazy; +use regex::Regex; + +use crate::args::TsConfig; +use crate::args::TypeCheckMode; +use crate::cache::FastInsecureHasher; +use crate::cache::TypeCheckCache; +use crate::diagnostics::Diagnostics; +use crate::graph_util::GraphData; +use crate::graph_util::ModuleEntry; +use crate::tsc; +use crate::tsc::Stats; +use crate::version; + +/// Options for performing a check of a module graph. Note that the decision to +/// emit or not is determined by the `ts_config` settings. +pub struct CheckOptions { + /// The check flag from the option which can effect the filtering of + /// diagnostics in the emit result. + pub type_check_mode: TypeCheckMode, + /// Set the debug flag on the TypeScript type checker. + pub debug: bool, + /// The module specifier to the configuration file, passed to tsc so that + /// configuration related diagnostics are properly formed. + pub maybe_config_specifier: Option, + /// The derived tsconfig that should be used when checking. + pub ts_config: TsConfig, + /// If true, `Check ` will be written to stdout for each root. + pub log_checks: bool, + /// If true, valid `.tsbuildinfo` files will be ignored and type checking + /// will always occur. + pub reload: bool, +} + +/// The result of a check of a module graph. +#[derive(Debug, Default)] +pub struct CheckResult { + pub diagnostics: Diagnostics, + pub stats: Stats, +} + +/// Given a set of roots and graph data, type check the module graph. +/// +/// It is expected that it is determined if a check and/or emit is validated +/// before the function is called. +pub fn check( + roots: &[(ModuleSpecifier, ModuleKind)], + graph_data: Arc>, + cache: &TypeCheckCache, + options: CheckOptions, +) -> Result { + let check_js = options.ts_config.get_check_js(); + let segment_graph_data = { + let graph_data = graph_data.read(); + graph_data.graph_segment(roots).unwrap() + }; + let check_hash = match get_check_hash(&segment_graph_data, &options) { + CheckHashResult::NoFiles => return Ok(Default::default()), + CheckHashResult::Hash(hash) => hash, + }; + + // do not type check if we know this is type checked + if !options.reload && cache.has_check_hash(check_hash) { + return Ok(Default::default()); + } + + let root_names = get_tsc_roots(&segment_graph_data, check_js); + if options.log_checks { + for (root, _) in roots { + let root_str = root.to_string(); + // `$deno` specifiers are internal, don't print them. + if !root_str.contains("$deno") { + log::info!("{} {}", colors::green("Check"), root); + } + } + } + // while there might be multiple roots, we can't "merge" the build info, so we + // try to retrieve the build info for first root, which is the most common use + // case. + let maybe_tsbuildinfo = if options.reload { + None + } else { + cache.get_tsbuildinfo(&roots[0].0) + }; + // to make tsc build info work, we need to consistently hash modules, so that + // tsc can better determine if an emit is still valid or not, so we provide + // that data here. + let hash_data = vec![ + options.ts_config.as_bytes(), + version::deno().as_bytes().to_owned(), + ]; + + let response = tsc::exec(tsc::Request { + config: options.ts_config, + debug: options.debug, + graph_data, + hash_data, + maybe_config_specifier: options.maybe_config_specifier, + maybe_tsbuildinfo, + root_names, + })?; + + let diagnostics = if options.type_check_mode == TypeCheckMode::Local { + response.diagnostics.filter(|d| { + if let Some(file_name) = &d.file_name { + !file_name.starts_with("http") + } else { + true + } + }) + } else { + response.diagnostics + }; + + if let Some(tsbuildinfo) = response.maybe_tsbuildinfo { + cache.set_tsbuildinfo(&roots[0].0, &tsbuildinfo); + } + + if diagnostics.is_empty() { + cache.add_check_hash(check_hash); + } + + Ok(CheckResult { + diagnostics, + stats: response.stats, + }) +} + +enum CheckHashResult { + Hash(u64), + NoFiles, +} + +/// Gets a hash of the inputs for type checking. This can then +/// be used to tell +fn get_check_hash( + graph_data: &GraphData, + options: &CheckOptions, +) -> CheckHashResult { + let mut hasher = FastInsecureHasher::new(); + hasher.write_u8(match options.type_check_mode { + TypeCheckMode::All => 0, + TypeCheckMode::Local => 1, + TypeCheckMode::None => 2, + }); + hasher.write(&options.ts_config.as_bytes()); + + let check_js = options.ts_config.get_check_js(); + let mut sorted_entries = graph_data.entries().collect::>(); + sorted_entries.sort_by_key(|(s, _)| s.as_str()); // make it deterministic + let mut has_file = false; + let mut has_file_to_type_check = false; + for (specifier, module_entry) in sorted_entries { + if let ModuleEntry::Module { + code, media_type, .. + } = module_entry + { + let ts_check = has_ts_check(*media_type, code); + if ts_check { + has_file_to_type_check = true; + } + + match media_type { + MediaType::TypeScript + | MediaType::Dts + | MediaType::Dmts + | MediaType::Dcts + | MediaType::Mts + | MediaType::Cts + | MediaType::Tsx => { + has_file = true; + has_file_to_type_check = true; + } + MediaType::JavaScript + | MediaType::Mjs + | MediaType::Cjs + | MediaType::Jsx => { + has_file = true; + if !check_js && !ts_check { + continue; + } + } + MediaType::Json + | MediaType::TsBuildInfo + | MediaType::SourceMap + | MediaType::Wasm + | MediaType::Unknown => continue, + } + hasher.write_str(specifier.as_str()); + hasher.write_str(code); + } + } + + if !has_file || !check_js && !has_file_to_type_check { + // no files to type check + CheckHashResult::NoFiles + } else { + CheckHashResult::Hash(hasher.finish()) + } +} + +/// Transform the graph into root specifiers that we can feed `tsc`. We have to +/// provide the media type for root modules because `tsc` does not "resolve" the +/// media type like other modules, as well as a root specifier needs any +/// redirects resolved. We need to include all the emittable files in +/// the roots, so they get type checked and optionally emitted, +/// otherwise they would be ignored if only imported into JavaScript. +fn get_tsc_roots( + graph_data: &GraphData, + check_js: bool, +) -> Vec<(ModuleSpecifier, MediaType)> { + graph_data + .entries() + .into_iter() + .filter_map(|(specifier, module_entry)| match module_entry { + ModuleEntry::Module { + media_type, code, .. + } => match media_type { + MediaType::TypeScript + | MediaType::Tsx + | MediaType::Mts + | MediaType::Cts + | MediaType::Jsx => Some((specifier.clone(), *media_type)), + MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs + if check_js || has_ts_check(*media_type, code) => + { + Some((specifier.clone(), *media_type)) + } + _ => None, + }, + _ => None, + }) + .collect() +} + +/// Matches the `@ts-check` pragma. +static TS_CHECK_RE: Lazy = + Lazy::new(|| Regex::new(r#"(?i)^\s*@ts-check(?:\s+|$)"#).unwrap()); + +fn has_ts_check(media_type: MediaType, file_text: &str) -> bool { + match &media_type { + MediaType::JavaScript + | MediaType::Mjs + | MediaType::Cjs + | MediaType::Jsx => get_leading_comments(file_text) + .iter() + .any(|text| TS_CHECK_RE.is_match(text)), + _ => false, + } +} + +fn get_leading_comments(file_text: &str) -> Vec { + let mut chars = file_text.chars().peekable(); + + // skip over the shebang + if file_text.starts_with("#!") { + // skip until the end of the line + for c in chars.by_ref() { + if c == '\n' { + break; + } + } + } + + let mut results = Vec::new(); + // now handle the comments + while chars.peek().is_some() { + // skip over any whitespace + while chars + .peek() + .map(|c| char::is_whitespace(*c)) + .unwrap_or(false) + { + chars.next(); + } + + if chars.next() != Some('/') { + break; + } + match chars.next() { + Some('/') => { + let mut text = String::new(); + for c in chars.by_ref() { + if c == '\n' { + break; + } else { + text.push(c); + } + } + results.push(text); + } + Some('*') => { + let mut text = String::new(); + while let Some(c) = chars.next() { + if c == '*' && chars.peek() == Some(&'/') { + chars.next(); + break; + } else { + text.push(c); + } + } + results.push(text); + } + _ => break, + } + } + results +} + +#[cfg(test)] +mod test { + use deno_ast::MediaType; + + use super::get_leading_comments; + use super::has_ts_check; + + #[test] + fn get_leading_comments_test() { + assert_eq!( + get_leading_comments( + "#!/usr/bin/env deno\r\n// test\n/* 1 *//*2*///3\n//\n /**/ /*4 */" + ), + vec![ + " test".to_string(), + " 1 ".to_string(), + "2".to_string(), + "3".to_string(), + "".to_string(), + "".to_string(), + "4 ".to_string(), + ] + ); + assert_eq!( + get_leading_comments("//1 /* */ \na;"), + vec!["1 /* */ ".to_string(),] + ); + assert_eq!(get_leading_comments("//"), vec!["".to_string()]); + } + + #[test] + fn has_ts_check_test() { + assert!(has_ts_check( + MediaType::JavaScript, + "// @ts-check\nconsole.log(5);" + )); + assert!(has_ts_check( + MediaType::JavaScript, + "// deno-lint-ignore\n// @ts-check\n" + )); + assert!(!has_ts_check( + MediaType::JavaScript, + "test;\n// @ts-check\n" + )); + assert!(!has_ts_check( + MediaType::JavaScript, + "// ts-check\nconsole.log(5);" + )); + } +} diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs index 11c9047769..8ef30189e0 100644 --- a/cli/tools/mod.rs +++ b/cli/tools/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. pub mod bench; +pub mod check; pub mod coverage; pub mod doc; pub mod fmt; diff --git a/cli/tsc.rs b/cli/tsc.rs index 5b99d0456d..54989a4ed8 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -2,7 +2,6 @@ use crate::args::TsConfig; use crate::diagnostics::Diagnostics; -use crate::emit; use crate::graph_util::GraphData; use crate::graph_util::ModuleEntry; @@ -15,7 +14,9 @@ use deno_core::op; use deno_core::parking_lot::RwLock; use deno_core::resolve_url_or_path; use deno_core::serde::Deserialize; +use deno_core::serde::Deserializer; use deno_core::serde::Serialize; +use deno_core::serde::Serializer; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; @@ -28,6 +29,7 @@ use deno_core::Snapshot; use deno_graph::Resolved; use once_cell::sync::Lazy; use std::collections::HashMap; +use std::fmt; use std::path::PathBuf; use std::sync::Arc; @@ -115,6 +117,40 @@ pub static STATIC_ASSETS: Lazy> = .collect() }); +/// A structure representing stats from a type check operation for a graph. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Stats(pub Vec<(String, u32)>); + +impl<'de> Deserialize<'de> for Stats { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?; + Ok(Stats(items)) + } +} + +impl Serialize for Stats { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Serialize::serialize(&self.0, serializer) + } +} + +impl fmt::Display for Stats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Compilation statistics:")?; + for (key, value) in self.0.clone() { + writeln!(f, " {}: {}", key, value)?; + } + + Ok(()) + } +} + /// Retrieve a static asset that are included in the binary. pub fn get_asset(asset: &str) -> Option<&'static str> { STATIC_ASSETS.get(asset).map(|s| s.to_owned()) @@ -255,7 +291,7 @@ pub struct Response { /// If there was any build info associated with the exec request. pub maybe_tsbuildinfo: Option, /// Statistics from the check. - pub stats: emit::Stats, + pub stats: Stats, } #[derive(Debug)] @@ -565,7 +601,7 @@ fn op_resolve( #[derive(Debug, Deserialize, Eq, PartialEq)] struct RespondArgs { pub diagnostics: Diagnostics, - pub stats: emit::Stats, + pub stats: Stats, } #[op] @@ -674,7 +710,6 @@ mod tests { use crate::args::TsConfig; use crate::diagnostics::Diagnostic; use crate::diagnostics::DiagnosticCategory; - use crate::emit::Stats; use deno_core::futures::future; use deno_core::OpState; use deno_graph::ModuleKind;