summaryrefslogtreecommitdiff
path: root/cli/proc_state.rs
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-10-11 08:26:22 +1100
committerGitHub <noreply@github.com>2021-10-11 08:26:22 +1100
commita7baf5f2bbb50dc0cb571de141b800b9155faca7 (patch)
tree4bebaabd1d3ed4595e8a388e0fae559bb5558974 /cli/proc_state.rs
parent5a8a989b7815023f33a1e3183a55cc8999af5d98 (diff)
refactor: integrate deno_graph into CLI (#12369)
Diffstat (limited to 'cli/proc_state.rs')
-rw-r--r--cli/proc_state.rs435
1 files changed, 270 insertions, 165 deletions
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index 2e1fb0e31..fe707754f 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -1,23 +1,24 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+use crate::cache;
use crate::colors;
use crate::compat;
use crate::config_file::ConfigFile;
use crate::deno_dir;
+use crate::emit;
+use crate::errors::get_module_graph_error_class;
use crate::file_fetcher::CacheSetting;
use crate::file_fetcher::FileFetcher;
use crate::flags;
use crate::http_cache;
+use crate::lockfile::as_maybe_locker;
use crate::lockfile::Lockfile;
-use crate::module_graph::CheckOptions;
-use crate::module_graph::GraphBuilder;
-use crate::module_graph::TranspileOptions;
-use crate::module_graph::TypeLib;
+use crate::resolver::ImportMapResolver;
use crate::source_maps::SourceMapGetter;
-use crate::specifier_handler::FetchHandler;
use crate::version;
use deno_core::error::anyhow;
+use deno_core::error::custom_error;
use deno_core::error::get_custom_error_class;
use deno_core::error::AnyError;
use deno_core::error::Context;
@@ -36,9 +37,6 @@ use deno_tls::rustls::RootCertStore;
use deno_tls::rustls_native_certs::load_native_certs;
use deno_tls::webpki_roots::TLS_SERVER_ROOTS;
use import_map::ImportMap;
-use log::debug;
-use log::info;
-use log::warn;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
@@ -59,12 +57,25 @@ pub struct Inner {
pub dir: deno_dir::DenoDir,
pub coverage_dir: Option<String>,
pub file_fetcher: FileFetcher,
- pub modules:
- Arc<Mutex<HashMap<ModuleSpecifier, Result<ModuleSource, AnyError>>>>,
+ modules: Arc<Mutex<HashMap<ModuleSpecifier, Result<ModuleSource, AnyError>>>>,
pub lockfile: Option<Arc<Mutex<Lockfile>>>,
pub maybe_config_file: Option<ConfigFile>,
pub maybe_import_map: Option<ImportMap>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
+ // deno_graph detects all sorts of issues at build time (prepare_module_load)
+ // but if they are errors at that stage, the don't cause the correct behaviors
+ // so we cache the error and then surface it when appropriate (e.g. load)
+ pub(crate) maybe_graph_error:
+ Arc<Mutex<Option<deno_graph::ModuleGraphError>>>,
+ // because the graph detects resolution issues early, but is build and dropped
+ // during the `prepare_module_load` method, we need to extract out the module
+ // resolution map so that those errors can be surfaced at the appropriate time
+ resolution_map:
+ Arc<Mutex<HashMap<ModuleSpecifier, HashMap<String, deno_graph::Resolved>>>>,
+ // in some cases we want to provide the span where the resolution error
+ // occurred but need to surface it on load, but on load we don't know who the
+ // referrer and span was, so we need to cache those
+ resolved_map: Arc<Mutex<HashMap<ModuleSpecifier, deno_graph::Span>>>,
pub root_cert_store: Option<RootCertStore>,
pub blob_store: BlobStore,
pub broadcast_channel: InMemoryBroadcastChannel,
@@ -222,11 +233,11 @@ impl ProcState {
let diagnostics = import_map.update_imports(node_builtins)?;
if !diagnostics.is_empty() {
- info!("Some Node built-ins were not added to the import map:");
+ log::info!("Some Node built-ins were not added to the import map:");
for diagnostic in diagnostics {
- info!(" - {}", diagnostic);
+ log::info!(" - {}", diagnostic);
}
- info!("If you want to use Node built-ins provided by Deno remove listed specifiers from \"imports\" mapping in the import map file.");
+ log::info!("If you want to use Node built-ins provided by Deno remove listed specifiers from \"imports\" mapping in the import map file.");
}
maybe_import_map = Some(import_map);
@@ -252,6 +263,9 @@ impl ProcState {
maybe_config_file,
maybe_import_map,
maybe_inspector_server,
+ maybe_graph_error: Default::default(),
+ resolution_map: Default::default(),
+ resolved_map: Default::default(),
root_cert_store: Some(root_cert_store.clone()),
blob_store,
broadcast_channel,
@@ -260,72 +274,174 @@ impl ProcState {
})))
}
- /// Prepares a set of module specifiers for loading in one shot.
- pub async fn prepare_module_graph(
+ /// Return any imports that should be brought into the scope of the module
+ /// graph.
+ fn get_maybe_imports(&self) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
+ let mut imports = Vec::new();
+ if let Some(config_file) = &self.maybe_config_file {
+ if let Some(config_imports) = config_file.to_maybe_imports() {
+ imports.extend(config_imports);
+ }
+ }
+ if self.flags.compat {
+ imports.extend(compat::get_node_imports());
+ }
+ if imports.is_empty() {
+ None
+ } else {
+ Some(imports)
+ }
+ }
+
+ /// This method is called when a module requested by the `JsRuntime` is not
+ /// available, or in other sub-commands that need to "load" a module graph.
+ /// The method will collect all the dependencies of the provided specifier,
+ /// optionally checks their integrity, optionally type checks them, and
+ /// ensures that any modules that needs to be transpiled is transpiled.
+ ///
+ /// It then populates the `loadable_modules` with what can be loaded into v8.
+ pub(crate) async fn prepare_module_load(
&self,
- specifiers: Vec<ModuleSpecifier>,
- lib: TypeLib,
+ roots: Vec<ModuleSpecifier>,
+ is_dynamic: bool,
+ lib: emit::TypeLib,
root_permissions: Permissions,
dynamic_permissions: Permissions,
- maybe_import_map: Option<ImportMap>,
) -> Result<(), AnyError> {
- let handler = Arc::new(Mutex::new(FetchHandler::new(
- self,
- root_permissions,
- dynamic_permissions,
- )?));
-
- let mut builder =
- GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone());
-
- for specifier in specifiers {
- builder.add(&specifier, false).await?;
- }
- builder.analyze_config_file(&self.maybe_config_file).await?;
-
- let mut graph = builder.get_graph();
- let debug = self.flags.log_level == Some(log::Level::Debug);
- let maybe_config_file = self.maybe_config_file.clone();
- let reload_exclusions = {
+ let mut cache = cache::FetchCacher::new(
+ self.dir.gen_cache.clone(),
+ self.file_fetcher.clone(),
+ root_permissions.clone(),
+ dynamic_permissions.clone(),
+ );
+ let maybe_locker = as_maybe_locker(self.lockfile.clone());
+ let maybe_imports = self.get_maybe_imports();
+ let maybe_resolver =
+ self.maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let graph = deno_graph::create_graph(
+ roots,
+ is_dynamic,
+ maybe_imports,
+ &mut cache,
+ maybe_resolver.as_ref().map(|im| im.as_resolver()),
+ maybe_locker,
+ None,
+ )
+ .await;
+ // If there was a locker, validate the integrity of all the modules in the
+ // locker.
+ emit::lock(&graph);
+
+ // Determine any modules that have already been emitted this session and
+ // should be skipped.
+ let reload_exclusions: HashSet<ModuleSpecifier> = {
let modules = self.modules.lock();
- modules.keys().cloned().collect::<HashSet<_>>()
+ modules.keys().cloned().collect()
};
- let result_modules = if self.flags.no_check {
- let result_info = graph.transpile(TranspileOptions {
- debug,
- maybe_config_file,
- reload: self.flags.reload,
- reload_exclusions,
- })?;
- debug!("{}", result_info.stats);
- if let Some(ignored_options) = result_info.maybe_ignored_options {
- warn!("{}", ignored_options);
- }
- result_info.loadable_modules
+ let config_type = if self.flags.no_check {
+ emit::ConfigType::Emit
} else {
- let result_info = graph.check(CheckOptions {
- debug,
- emit: true,
+ emit::ConfigType::Check {
+ tsc_emit: true,
lib,
- maybe_config_file,
- reload: self.flags.reload,
- reload_exclusions,
- })?;
-
- debug!("{}", result_info.stats);
- if let Some(ignored_options) = result_info.maybe_ignored_options {
- eprintln!("{}", ignored_options);
}
- if !result_info.diagnostics.is_empty() {
- return Err(anyhow!(result_info.diagnostics));
- }
- result_info.loadable_modules
};
- let mut loadable_modules = self.modules.lock();
- loadable_modules.extend(result_modules);
+ let (ts_config, maybe_ignored_options) =
+ emit::get_ts_config(config_type, self.maybe_config_file.as_ref(), None)?;
+ let graph = Arc::new(graph);
+
+ // we will store this in proc state later, as if we were to return it from
+ // prepare_load, some dynamic errors would not be catchable
+ let maybe_graph_error = graph.valid().err();
+
+ if emit::valid_emit(
+ graph.as_ref(),
+ &cache,
+ &ts_config,
+ self.flags.reload,
+ &reload_exclusions,
+ ) {
+ if let Some(root) = graph.roots.get(0) {
+ log::debug!("specifier \"{}\" and dependencies have valid emit, skipping checking and emitting", root);
+ } else {
+ log::debug!("rootless graph, skipping checking and emitting");
+ }
+ } else {
+ if let Some(ignored_options) = maybe_ignored_options {
+ log::warn!("{}", ignored_options);
+ }
+ let emit_result = if self.flags.no_check {
+ let options = emit::EmitOptions {
+ ts_config,
+ reload_exclusions,
+ reload: self.flags.reload,
+ };
+ emit::emit(graph.as_ref(), &mut cache, options)?
+ } else {
+ // here, we are type checking, so we want to error here if any of the
+ // type only dependencies are missing or we have other errors with them
+ // where as if we are not type checking, we shouldn't care about these
+ // errors, and they don't get returned in `graph.valid()` above.
+ graph.valid_types_only()?;
+
+ let maybe_config_specifier = self
+ .maybe_config_file
+ .as_ref()
+ .map(|cf| ModuleSpecifier::from_file_path(&cf.path).unwrap());
+ let options = emit::CheckOptions {
+ debug: self.flags.log_level == Some(log::Level::Debug),
+ emit_with_diagnostics: true,
+ maybe_config_specifier,
+ ts_config,
+ };
+ for root in &graph.roots {
+ let root_str = root.to_string();
+ // `$deno$` specifiers are internal specifiers, printing out that
+ // they are being checked is confusing to a user, since they don't
+ // actually exist, so we will simply indicate that a generated module
+ // is being checked instead of the cryptic internal module
+ if !root_str.contains("$deno$") {
+ log::info!("{} {}", colors::green("Check"), root);
+ } else {
+ log::info!("{} a generated module", colors::green("Check"))
+ }
+ }
+ emit::check_and_maybe_emit(graph.clone(), &mut cache, options)?
+ };
+ log::debug!("{}", emit_result.stats);
+ // if the graph is not valid then the diagnostics returned are bogus and
+ // should just be ignored so that module loading can proceed to allow the
+ // "real" error to be surfaced
+ if !emit_result.diagnostics.is_empty() && maybe_graph_error.is_none() {
+ return Err(anyhow!(emit_result.diagnostics));
+ }
+ }
+
+ // we iterate over the graph, looking for any modules that were emitted, or
+ // should be loaded as their un-emitted source and add them to the in memory
+ // cache of modules for loading by deno_core.
+ {
+ let mut modules = self.modules.lock();
+ modules.extend(emit::to_module_sources(graph.as_ref(), &cache));
+ }
+ // since we can't store the graph in proc state, because proc state needs to
+ // be thread safe because of the need to provide source map resolution and
+ // the graph needs to not be thread safe (due to wasmbind_gen constraints),
+ // we have no choice but to extract out other meta data from the graph to
+ // provide the correct loading behaviors for CLI
+ {
+ let mut resolution_map = self.resolution_map.lock();
+ resolution_map.extend(graph.resolution_map());
+ }
+ {
+ let mut self_maybe_graph_error = self.maybe_graph_error.lock();
+ *self_maybe_graph_error = maybe_graph_error;
+ }
+
+ // any updates to the lockfile should be updated now
if let Some(ref lockfile) = self.lockfile {
let g = lockfile.lock();
g.write()?;
@@ -334,127 +450,116 @@ impl ProcState {
Ok(())
}
- /// This function is called when new module load is initialized by the JsRuntime. Its
- /// resposibility is to collect all dependencies and if it is required then also perform TS
- /// typecheck and traspilation.
- pub async fn prepare_module_load(
+ pub(crate) fn resolve(
&self,
- specifier: ModuleSpecifier,
- lib: TypeLib,
- root_permissions: Permissions,
- dynamic_permissions: Permissions,
- is_dynamic: bool,
- maybe_import_map: Option<ImportMap>,
- ) -> Result<(), AnyError> {
- let specifier = specifier.clone();
- let handler = Arc::new(Mutex::new(FetchHandler::new(
- self,
- root_permissions,
- dynamic_permissions,
- )?));
- let mut builder =
- GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone());
- if self.flags.compat {
- builder.add(&compat::get_node_globals_url(), false).await?;
- }
- builder.add(&specifier, is_dynamic).await?;
- builder.analyze_config_file(&self.maybe_config_file).await?;
- let mut graph = builder.get_graph();
- let debug = self.flags.log_level == Some(log::Level::Debug);
- let maybe_config_file = self.maybe_config_file.clone();
- let reload_exclusions = {
- let modules = self.modules.lock();
- modules.keys().cloned().collect::<HashSet<_>>()
- };
-
- let result_modules = if self.flags.no_check {
- let result_info = graph.transpile(TranspileOptions {
- debug,
- maybe_config_file,
- reload: self.flags.reload,
- reload_exclusions,
- })?;
- debug!("{}", result_info.stats);
- if let Some(ignored_options) = result_info.maybe_ignored_options {
- warn!("{}", ignored_options);
+ specifier: &str,
+ referrer: &str,
+ ) -> Result<ModuleSpecifier, AnyError> {
+ let resolution_map = self.resolution_map.lock();
+ if let Some((_, Some(map))) = deno_core::resolve_url_or_path(referrer)
+ .ok()
+ .map(|s| (s.clone(), resolution_map.get(&s)))
+ {
+ if let Some(resolved) = map.get(specifier) {
+ match resolved {
+ Some(Ok((specifier, span))) => {
+ let mut resolved_map = self.resolved_map.lock();
+ resolved_map.insert(specifier.clone(), span.clone());
+ return Ok(specifier.clone());
+ }
+ Some(Err(err)) => {
+ return Err(custom_error(
+ "TypeError",
+ format!("{}\n", err.to_string_with_span()),
+ ))
+ }
+ _ => (),
+ }
}
- result_info.loadable_modules
+ }
+ // FIXME(bartlomieju): hacky way to provide compatibility with repl
+ let referrer = if referrer.is_empty() && self.flags.repl {
+ deno_core::DUMMY_SPECIFIER
} else {
- let result_info = graph.check(CheckOptions {
- debug,
- emit: true,
- lib,
- maybe_config_file,
- reload: self.flags.reload,
- reload_exclusions,
- })?;
-
- debug!("{}", result_info.stats);
- if let Some(ignored_options) = result_info.maybe_ignored_options {
- eprintln!("{}", ignored_options);
- }
- if !result_info.diagnostics.is_empty() {
- return Err(anyhow!(result_info.diagnostics));
- }
- result_info.loadable_modules
+ referrer
};
-
- let mut loadable_modules = self.modules.lock();
- loadable_modules.extend(result_modules);
-
- if let Some(ref lockfile) = self.lockfile {
- let g = lockfile.lock();
- g.write()?;
+ if let Some(import_map) = &self.maybe_import_map {
+ import_map
+ .resolve(specifier, referrer)
+ .map_err(|err| err.into())
+ } else {
+ deno_core::resolve_import(specifier, referrer).map_err(|err| err.into())
}
-
- Ok(())
}
pub fn load(
&self,
specifier: ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
+ is_dynamic: bool,
) -> Result<ModuleSource, AnyError> {
+ log::debug!(
+ "specifier: {} maybe_referrer: {} is_dynamic: {}",
+ specifier,
+ maybe_referrer
+ .as_ref()
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| "<none>".to_string()),
+ is_dynamic
+ );
let modules = self.modules.lock();
modules
.get(&specifier)
.map(|r| match r {
Ok(module_source) => Ok(module_source.clone()),
Err(err) => {
- // TODO(@kitsonk) this feels a bit hacky but it works, without
- // introducing another enum to have to try to deal with.
- if get_custom_error_class(err) == Some("NotFound") {
- let message = if let Some(referrer) = &maybe_referrer {
- format!("{}\n From: {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err, referrer)
- } else {
- format!("{}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err)
- };
- warn!("{}: {}", crate::colors::yellow("warning"), message);
- Ok(ModuleSource {
- code: "".to_string(),
- module_url_found: specifier.to_string(),
- module_url_specified: specifier.to_string(),
- })
+ // this is the "pending" error we will return
+ let err = if let Some(error_class) = get_custom_error_class(err) {
+ if error_class == "NotFound" && maybe_referrer.is_some() && !is_dynamic {
+ let resolved_map = self.resolved_map.lock();
+ // in situations where we were to try to load a module that wasn't
+ // emitted and we can't run the original source code (it isn't)
+ // JavaScript, we will load a blank module instead. This is
+ // usually caused by people exporting type only exports and not
+ // type checking.
+ if let Some(span) = resolved_map.get(&specifier) {
+ log::warn!("{}: Cannot load module \"{}\".\n at {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", colors::yellow("warning"), specifier, span);
+ return Ok(ModuleSource {
+ code: "".to_string(),
+ module_url_found: specifier.to_string(),
+ module_url_specified: specifier.to_string(),
+ });
+ }
+ }
+ custom_error(error_class, err.to_string())
+ } else {
+ anyhow!(err.to_string())
+ };
+ // if there is a pending graph error though we haven't returned, we
+ // will return that one
+ let mut maybe_graph_error = self.maybe_graph_error.lock();
+ if let Some(graph_error) = maybe_graph_error.take() {
+ log::debug!("returning cached graph error");
+ let resolved_map = self.resolved_map.lock();
+ if let Some(span) = resolved_map.get(&specifier) {
+ if !span.specifier.as_str().contains("$deno") {
+ return Err(custom_error(get_module_graph_error_class(&graph_error), format!("{}\n at {}", graph_error, span)));
+ }
+ }
+ Err(graph_error.into())
} else {
- // anyhow errors don't support cloning, so we have to manage this
- // ourselves
- Err(anyhow!(err.to_string()))
+ Err(err)
}
- },
+ }
})
.unwrap_or_else(|| {
- if let Some(referrer) = maybe_referrer {
- Err(anyhow!(
- "Module \"{}\" is missing from the graph.\n From: {}",
- specifier,
- referrer
- ))
- } else {
- Err(anyhow!(
- "Module \"{}\" is missing from the graph.",
- specifier
- ))
+ if maybe_referrer.is_some() && !is_dynamic {
+ let resolved_map = self.resolved_map.lock();
+ if let Some(span) = resolved_map.get(&specifier) {
+ return Err(custom_error("NotFound", format!("Cannot load module \"{}\".\n at {}", specifier, span)));
+ }
}
+ Err(custom_error("NotFound", format!("Cannot load module \"{}\".", specifier)))
})
}
@@ -497,7 +602,7 @@ impl SourceMapGetter for ProcState {
if let Some((code, maybe_map)) = self.get_emit(&specifier) {
let code = String::from_utf8(code).unwrap();
source_map_from_code(code).or(maybe_map)
- } else if let Ok(source) = self.load(specifier, None) {
+ } else if let Ok(source) = self.load(specifier, None, false) {
source_map_from_code(source.code)
} else {
None