diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2021-12-16 10:45:41 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-16 21:45:41 +1100 |
commit | 9ffc7edc23444be8bdcc1befd3709b309780e3d1 (patch) | |
tree | 551a223016816b92a5f8cf57949c331bf087a960 /cli/graph_util.rs | |
parent | e28fb70aeecf548d150312c30f7f32b60c4fdece (diff) |
refactor(cli): use GraphData for check and emit (#12960)
Diffstat (limited to 'cli/graph_util.rs')
-rw-r--r-- | cli/graph_util.rs | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/cli/graph_util.rs b/cli/graph_util.rs new file mode 100644 index 000000000..941243d90 --- /dev/null +++ b/cli/graph_util.rs @@ -0,0 +1,412 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::colors; +use crate::emit::TypeLib; +use crate::errors::get_error_class_name; +use deno_core::error::custom_error; +use deno_core::error::AnyError; +use deno_core::ModuleSpecifier; +use deno_graph::Dependency; +use deno_graph::MediaType; +use deno_graph::Module; +use deno_graph::ModuleGraph; +use deno_graph::ModuleGraphError; +use deno_graph::Range; +use deno_graph::ResolutionError; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; +use std::sync::Arc; + +#[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub(crate) enum ModuleEntry { + Module { + code: Arc<String>, + dependencies: BTreeMap<String, Dependency>, + media_type: MediaType, + /// A set of type libs that the module has passed a type check with this + /// session. This would consist of window, worker or both. + checked_libs: HashSet<TypeLib>, + maybe_types: Option<Result<(ModuleSpecifier, Range), ResolutionError>>, + }, + Configuration { + dependencies: + BTreeMap<String, Result<(ModuleSpecifier, Range), ResolutionError>>, + }, + Error(ModuleGraphError), + Redirect(ModuleSpecifier), +} + +/// Composes data from potentially many `ModuleGraph`s. +#[derive(Debug, Default)] +pub(crate) struct GraphData { + modules: HashMap<ModuleSpecifier, ModuleEntry>, + /// Map of first known referrer locations for each module. Used to enhance + /// error messages. + referrer_map: HashMap<ModuleSpecifier, Range>, + configurations: HashSet<ModuleSpecifier>, +} + +impl GraphData { + /// Store data from `graph` into `self`. + pub(crate) fn add_graph(&mut self, graph: &ModuleGraph, reload: bool) { + for (specifier, result) in graph.specifiers() { + if !reload && self.modules.contains_key(&specifier) { + continue; + } + if let Some(found) = graph.redirects.get(&specifier) { + let module_entry = ModuleEntry::Redirect(found.clone()); + self.modules.insert(specifier.clone(), module_entry); + continue; + } + match result { + Ok((_, media_type)) => { + let module = graph.get(&specifier).unwrap(); + let (code, dependencies, maybe_types) = match module { + Module::Es(es_module) => ( + es_module.source.clone(), + es_module.dependencies.clone(), + es_module + .maybe_types_dependency + .as_ref() + .and_then(|(_, r)| r.clone()), + ), + Module::Synthetic(synthetic_module) => match &synthetic_module + .maybe_source + { + // Synthetic modules with a source are actually JSON modules. + Some(source) => (source.clone(), Default::default(), None), + // Synthetic modules without a source are config roots. + None => { + let mut dependencies = BTreeMap::new(); + for (specifier, resolved) in &synthetic_module.dependencies { + if let Some(dep_result) = resolved { + dependencies.insert(specifier.clone(), dep_result.clone()); + if let Ok((specifier, referrer_range)) = dep_result { + let entry = self.referrer_map.entry(specifier.clone()); + entry.or_insert_with(|| referrer_range.clone()); + } + } + } + self.modules.insert( + synthetic_module.specifier.clone(), + ModuleEntry::Configuration { dependencies }, + ); + self + .configurations + .insert(synthetic_module.specifier.clone()); + continue; + } + }, + }; + if let Some(Ok((specifier, referrer_range))) = &maybe_types { + let specifier = graph.redirects.get(specifier).unwrap_or(specifier); + let entry = self.referrer_map.entry(specifier.clone()); + entry.or_insert_with(|| referrer_range.clone()); + } + for dep in dependencies.values() { + #[allow(clippy::manual_flatten)] + for resolved in [&dep.maybe_code, &dep.maybe_type] { + if let Some(Ok((specifier, referrer_range))) = resolved { + let specifier = + graph.redirects.get(specifier).unwrap_or(specifier); + let entry = self.referrer_map.entry(specifier.clone()); + entry.or_insert_with(|| referrer_range.clone()); + } + } + } + let module_entry = ModuleEntry::Module { + code, + dependencies, + media_type, + checked_libs: Default::default(), + maybe_types, + }; + self.modules.insert(specifier, module_entry); + } + Err(error) => { + let module_entry = ModuleEntry::Error(error); + self.modules.insert(specifier, module_entry); + } + } + } + } + + pub(crate) fn entries(&self) -> HashMap<&ModuleSpecifier, &ModuleEntry> { + self.modules.iter().collect() + } + + /// Walk dependencies from `roots` and return every encountered specifier. + /// Return `None` if any modules are not known. + pub(crate) fn walk<'a>( + &'a self, + roots: &[ModuleSpecifier], + follow_dynamic: bool, + follow_type_only: bool, + ) -> Option<HashMap<&'a ModuleSpecifier, &'a ModuleEntry>> { + let mut result = HashMap::<&'a ModuleSpecifier, &'a ModuleEntry>::new(); + let mut seen = HashSet::<&ModuleSpecifier>::new(); + let mut visiting = VecDeque::<&ModuleSpecifier>::new(); + for root in roots { + seen.insert(root); + visiting.push_back(root); + } + for root in &self.configurations { + seen.insert(root); + visiting.push_back(root); + } + while let Some(specifier) = visiting.pop_front() { + let (specifier, entry) = match self.modules.get_key_value(specifier) { + Some(pair) => pair, + None => return None, + }; + result.insert(specifier, entry); + match entry { + ModuleEntry::Module { + dependencies, + maybe_types, + .. + } => { + if follow_type_only { + if let Some(Ok((types, _))) = maybe_types { + if !seen.contains(types) { + seen.insert(types); + visiting.push_front(types); + } + } + } + for (_, dep) in dependencies.iter().rev() { + if !dep.is_dynamic || follow_dynamic { + let mut resolutions = vec![&dep.maybe_code]; + if follow_type_only { + resolutions.push(&dep.maybe_type); + } + #[allow(clippy::manual_flatten)] + for resolved in resolutions { + if let Some(Ok((dep_specifier, _))) = resolved { + if !seen.contains(dep_specifier) { + seen.insert(dep_specifier); + visiting.push_front(dep_specifier); + } + } + } + } + } + } + ModuleEntry::Configuration { dependencies } => { + for (dep_specifier, _) in dependencies.values().flatten() { + if !seen.contains(dep_specifier) { + seen.insert(dep_specifier); + visiting.push_front(dep_specifier); + } + } + } + ModuleEntry::Error(_) => {} + ModuleEntry::Redirect(specifier) => { + if !seen.contains(specifier) { + seen.insert(specifier); + visiting.push_front(specifier); + } + } + } + } + Some(result) + } + + /// Clone part of `self`, containing only modules which are dependencies of + /// `roots`. Returns `None` if any roots are not known. + pub(crate) fn graph_segment( + &self, + roots: &[ModuleSpecifier], + ) -> Option<Self> { + let mut modules = HashMap::new(); + let mut referrer_map = HashMap::new(); + let entries = match self.walk(roots, true, true) { + Some(entries) => entries, + None => return None, + }; + for (specifier, module_entry) in entries { + modules.insert(specifier.clone(), module_entry.clone()); + if let Some(referrer) = self.referrer_map.get(specifier) { + referrer_map.insert(specifier.clone(), referrer.clone()); + } + } + Some(Self { + modules, + referrer_map, + configurations: self.configurations.clone(), + }) + } + + /// Check if `roots` and their deps are available. Returns `Some(Ok(()))` if + /// so. Returns `Some(Err(_))` if there is a known module graph or resolution + /// error statically reachable from `roots`. Returns `None` if any modules are + /// not known. + pub(crate) fn check( + &self, + roots: &[ModuleSpecifier], + follow_type_only: bool, + ) -> Option<Result<(), AnyError>> { + let entries = match self.walk(roots, false, follow_type_only) { + Some(entries) => entries, + None => return None, + }; + for (specifier, module_entry) in entries { + match module_entry { + ModuleEntry::Module { + dependencies, + maybe_types, + .. + } => { + if follow_type_only { + if let Some(Err(error)) = maybe_types { + let range = error.range(); + if !range.specifier.as_str().contains("$deno") { + return Some(Err(custom_error( + get_error_class_name(&error.clone().into()), + format!("{}\n at {}", error.to_string(), range), + ))); + } + return Some(Err(error.clone().into())); + } + } + for (_, dep) in dependencies.iter() { + if !dep.is_dynamic { + let mut resolutions = vec![&dep.maybe_code]; + if follow_type_only { + resolutions.push(&dep.maybe_type); + } + #[allow(clippy::manual_flatten)] + for resolved in resolutions { + if let Some(Err(error)) = resolved { + let range = error.range(); + if !range.specifier.as_str().contains("$deno") { + return Some(Err(custom_error( + get_error_class_name(&error.clone().into()), + format!("{}\n at {}", error.to_string(), range), + ))); + } + return Some(Err(error.clone().into())); + } + } + } + } + } + ModuleEntry::Configuration { dependencies } => { + for resolved_result in dependencies.values() { + if let Err(error) = resolved_result { + let range = error.range(); + if !range.specifier.as_str().contains("$deno") { + return Some(Err(custom_error( + get_error_class_name(&error.clone().into()), + format!("{}\n at {}", error.to_string(), range), + ))); + } + return Some(Err(error.clone().into())); + } + } + } + ModuleEntry::Error(error) => { + if !roots.contains(specifier) { + if let Some(range) = self.referrer_map.get(specifier) { + if !range.specifier.as_str().contains("$deno") { + let message = error.to_string(); + return Some(Err(custom_error( + get_error_class_name(&error.clone().into()), + format!("{}\n at {}", message, range), + ))); + } + } + } + return Some(Err(error.clone().into())); + } + _ => {} + } + } + Some(Ok(())) + } + + /// Mark `roots` and all of their dependencies as type checked under `lib`. + /// Assumes that all of those modules are known. + pub(crate) fn set_type_checked( + &mut self, + roots: &[ModuleSpecifier], + lib: &TypeLib, + ) { + let specifiers: Vec<ModuleSpecifier> = match self.walk(roots, true, true) { + Some(entries) => entries.into_keys().cloned().collect(), + None => unreachable!("contains module not in graph data"), + }; + for specifier in specifiers { + if let ModuleEntry::Module { checked_libs, .. } = + self.modules.get_mut(&specifier).unwrap() + { + checked_libs.insert(lib.clone()); + } + } + } + + /// Check if `roots` are all marked as type checked under `lib`. + pub(crate) fn is_type_checked( + &self, + roots: &[ModuleSpecifier], + lib: &TypeLib, + ) -> bool { + roots.iter().all(|r| { + let found = self.follow_redirect(r); + match self.modules.get(&found) { + Some(ModuleEntry::Module { checked_libs, .. }) => { + checked_libs.contains(lib) + } + _ => false, + } + }) + } + + /// If `specifier` is known and a redirect, return the found specifier. + /// Otherwise return `specifier`. + pub(crate) fn follow_redirect( + &self, + specifier: &ModuleSpecifier, + ) -> ModuleSpecifier { + match self.modules.get(specifier) { + Some(ModuleEntry::Redirect(s)) => s.clone(), + _ => specifier.clone(), + } + } + + pub(crate) fn get<'a>( + &'a self, + specifier: &ModuleSpecifier, + ) -> Option<&'a ModuleEntry> { + self.modules.get(specifier) + } +} + +impl From<&ModuleGraph> for GraphData { + fn from(graph: &ModuleGraph) -> Self { + let mut graph_data = GraphData::default(); + graph_data.add_graph(graph, false); + graph_data + } +} + +/// Like `graph.valid()`, but enhanced with referrer info. +pub(crate) fn graph_valid( + graph: &ModuleGraph, + follow_type_only: bool, +) -> Result<(), AnyError> { + GraphData::from(graph) + .check(&graph.roots, follow_type_only) + .unwrap() +} + +/// Calls `graph.lock()` and exits on errors. +pub(crate) fn graph_lock_or_exit(graph: &ModuleGraph) { + if let Err(err) = graph.lock() { + log::error!("{} {}", colors::red("error:"), err); + std::process::exit(10); + } +} |