diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-09-02 10:54:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-02 10:54:40 -0400 |
commit | 387300aed0133e369af090d3795a1fce89603737 (patch) | |
tree | 63061cf6bb6853504f507771af91c9f2682880c4 /cli/emit.rs | |
parent | e719a02bb0a4cc39e11a945dcff83422440e50d4 (diff) |
refactor: extract out check code from emit (#15729)
Closes #15535
Diffstat (limited to 'cli/emit.rs')
-rw-r--r-- | cli/emit.rs | 394 |
1 files changed, 0 insertions, 394 deletions
diff --git a/cli/emit.rs b/cli/emit.rs index eb8a56ad0..f2d890adc 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<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?; - Ok(Stats(items)) - } -} - -impl Serialize for Stats { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - 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<ModuleSpecifier>, - /// The derived tsconfig that should be used when checking. - pub ts_config: TsConfig, - /// If true, `Check <specifier>` 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<RwLock<GraphData>>, - cache: &TypeCheckCache, - options: CheckOptions, -) -> Result<CheckResult, AnyError> { - 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::<Vec<_>>(); - 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<TsConfig> for deno_ast::EmitOptions { } } } - -/// Matches the `@ts-check` pragma. -static TS_CHECK_RE: Lazy<Regex> = - 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<String> { - 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);" - )); - } -} |