mirror of
https://github.com/denoland/deno.git
synced 2024-10-30 09:08:00 -04:00
a1f0796fcc
This commit adds proper support for import assertions and JSON modules. Implementation of "core/modules.rs" was changed to account for multiple possible module types, instead of always assuming that the code is an "ES module". In effect "ModuleMap" now has knowledge about each modules' type (stored via "ModuleType" enum). Module loading pipeline now stores information about expected module type for each request and validates that expected type matches discovered module type based on file's "MediaType". Relevant tests were added to "core/modules.rs" and integration tests, additionally multiple WPT tests were enabled. There are still some rough edges in the implementation and not all WPT were enabled, due to: a) unclear BOM handling in source code by "FileFetcher" b) design limitation of Deno's "FileFetcher" that doesn't download the same module multiple times in a single run Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
842 lines
27 KiB
Rust
842 lines
27 KiB
Rust
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use crate::cache;
|
|
use crate::cache::Cacher;
|
|
use crate::colors;
|
|
use crate::compat;
|
|
use crate::compat::NodeEsmResolver;
|
|
use crate::config_file::ConfigFile;
|
|
use crate::config_file::MaybeImportsResult;
|
|
use crate::deno_dir;
|
|
use crate::emit;
|
|
use crate::errors::get_error_class_name;
|
|
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::resolver::ImportMapResolver;
|
|
use crate::resolver::JsxResolver;
|
|
use crate::source_maps::SourceMapGetter;
|
|
use crate::version;
|
|
|
|
use deno_core::anyhow::anyhow;
|
|
use deno_core::anyhow::Context;
|
|
use deno_core::error::custom_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::parking_lot::Mutex;
|
|
use deno_core::resolve_url;
|
|
use deno_core::url::Url;
|
|
use deno_core::CompiledWasmModuleStore;
|
|
use deno_core::ModuleSource;
|
|
use deno_core::ModuleSpecifier;
|
|
use deno_core::ModuleType;
|
|
use deno_core::SharedArrayBufferStore;
|
|
use deno_graph::create_graph;
|
|
use deno_graph::Dependency;
|
|
use deno_graph::MediaType;
|
|
use deno_graph::ModuleGraphError;
|
|
use deno_graph::Range;
|
|
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
|
|
use deno_runtime::deno_tls::rustls;
|
|
use deno_runtime::deno_tls::rustls::RootCertStore;
|
|
use deno_runtime::deno_tls::rustls_native_certs::load_native_certs;
|
|
use deno_runtime::deno_tls::rustls_pemfile;
|
|
use deno_runtime::deno_tls::webpki_roots;
|
|
use deno_runtime::deno_web::BlobStore;
|
|
use deno_runtime::inspector_server::InspectorServer;
|
|
use deno_runtime::permissions::Permissions;
|
|
use import_map::ImportMap;
|
|
use std::collections::BTreeMap;
|
|
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
use std::collections::VecDeque;
|
|
use std::env;
|
|
use std::fs::File;
|
|
use std::io::BufReader;
|
|
use std::ops::Deref;
|
|
use std::sync::Arc;
|
|
|
|
/// This structure represents state of single "deno" program.
|
|
///
|
|
/// It is shared by all created workers (thus V8 isolates).
|
|
#[derive(Clone)]
|
|
pub struct ProcState(Arc<Inner>);
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[allow(clippy::large_enum_variant)]
|
|
enum ModuleEntry {
|
|
Module {
|
|
code: String,
|
|
media_type: MediaType,
|
|
dependencies: BTreeMap<String, Dependency>,
|
|
},
|
|
Error(ModuleGraphError),
|
|
Redirect(ModuleSpecifier),
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct GraphData {
|
|
modules: HashMap<ModuleSpecifier, ModuleEntry>,
|
|
/// A set of type libs that each module has passed a type check with this
|
|
/// session. This would consist of window, worker or both.
|
|
checked_libs_map: HashMap<ModuleSpecifier, HashSet<emit::TypeLib>>,
|
|
/// Map of first known referrer locations for each module. Used to enhance
|
|
/// error messages.
|
|
referrer_map: HashMap<ModuleSpecifier, Range>,
|
|
}
|
|
|
|
impl GraphData {
|
|
/// Check if `roots` are ready to be loaded by V8. Returns `Some(Ok(()))` if
|
|
/// prepared. Returns `Some(Err(_))` if there is a known module graph error
|
|
/// statically reachable from `roots`. Returns `None` if sufficient graph data
|
|
/// is yet to be supplied.
|
|
fn check_if_prepared(
|
|
&self,
|
|
roots: &[ModuleSpecifier],
|
|
) -> Option<Result<(), AnyError>> {
|
|
let mut seen = HashSet::<&ModuleSpecifier>::new();
|
|
let mut visiting = VecDeque::<&ModuleSpecifier>::new();
|
|
for root in roots {
|
|
visiting.push_back(root);
|
|
}
|
|
while let Some(specifier) = visiting.pop_front() {
|
|
match self.modules.get(specifier) {
|
|
Some(ModuleEntry::Module { dependencies, .. }) => {
|
|
for (_, dep) in dependencies.iter().rev() {
|
|
for resolved in [&dep.maybe_code, &dep.maybe_type] {
|
|
if !dep.is_dynamic {
|
|
match resolved {
|
|
Some(Ok((dep_specifier, _))) => {
|
|
if !dep.is_dynamic && !seen.contains(dep_specifier) {
|
|
seen.insert(dep_specifier);
|
|
visiting.push_front(dep_specifier);
|
|
}
|
|
}
|
|
Some(Err(error)) => {
|
|
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()));
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(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(ModuleEntry::Redirect(specifier)) => {
|
|
seen.insert(specifier);
|
|
visiting.push_front(specifier);
|
|
}
|
|
None => return None,
|
|
}
|
|
}
|
|
Some(Ok(()))
|
|
}
|
|
}
|
|
|
|
pub struct Inner {
|
|
/// Flags parsed from `argv` contents.
|
|
pub flags: flags::Flags,
|
|
pub dir: deno_dir::DenoDir,
|
|
pub coverage_dir: Option<String>,
|
|
pub file_fetcher: FileFetcher,
|
|
graph_data: Arc<Mutex<GraphData>>,
|
|
pub lockfile: Option<Arc<Mutex<Lockfile>>>,
|
|
pub maybe_config_file: Option<ConfigFile>,
|
|
pub maybe_import_map: Option<Arc<ImportMap>>,
|
|
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
|
|
pub root_cert_store: Option<RootCertStore>,
|
|
pub blob_store: BlobStore,
|
|
pub broadcast_channel: InMemoryBroadcastChannel,
|
|
pub shared_array_buffer_store: SharedArrayBufferStore,
|
|
pub compiled_wasm_module_store: CompiledWasmModuleStore,
|
|
maybe_resolver: Option<Arc<dyn deno_graph::source::Resolver + Send + Sync>>,
|
|
}
|
|
|
|
impl Deref for ProcState {
|
|
type Target = Arc<Inner>;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl ProcState {
|
|
pub async fn build(flags: flags::Flags) -> Result<Self, AnyError> {
|
|
let maybe_custom_root = flags
|
|
.cache_path
|
|
.clone()
|
|
.or_else(|| env::var("DENO_DIR").map(String::into).ok());
|
|
let dir = deno_dir::DenoDir::new(maybe_custom_root)?;
|
|
let deps_cache_location = dir.root.join("deps");
|
|
let http_cache = http_cache::HttpCache::new(&deps_cache_location);
|
|
|
|
let mut root_cert_store = RootCertStore::empty();
|
|
let ca_stores: Vec<String> = flags
|
|
.ca_stores
|
|
.clone()
|
|
.or_else(|| {
|
|
let env_ca_store = env::var("DENO_TLS_CA_STORE").ok()?;
|
|
Some(
|
|
env_ca_store
|
|
.split(',')
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty())
|
|
.collect(),
|
|
)
|
|
})
|
|
.unwrap_or_else(|| vec!["mozilla".to_string()]);
|
|
|
|
for store in ca_stores.iter() {
|
|
match store.as_str() {
|
|
"mozilla" => {
|
|
root_cert_store.add_server_trust_anchors(
|
|
webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
|
|
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
|
|
ta.subject,
|
|
ta.spki,
|
|
ta.name_constraints,
|
|
)
|
|
}),
|
|
);
|
|
}
|
|
"system" => {
|
|
let roots =
|
|
load_native_certs().expect("could not load platform certs");
|
|
for root in roots {
|
|
root_cert_store
|
|
.add(&rustls::Certificate(root.0))
|
|
.expect("Failed to add platform cert to root cert store");
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Unknown certificate store \"{}\" specified (allowed: \"system,mozilla\")", store));
|
|
}
|
|
}
|
|
}
|
|
|
|
let ca_file = flags.ca_file.clone().or_else(|| env::var("DENO_CERT").ok());
|
|
if let Some(ca_file) = ca_file {
|
|
let certfile = File::open(&ca_file)?;
|
|
let mut reader = BufReader::new(certfile);
|
|
|
|
match rustls_pemfile::certs(&mut reader) {
|
|
Ok(certs) => {
|
|
root_cert_store.add_parsable_certificates(&certs);
|
|
}
|
|
Err(e) => {
|
|
return Err(anyhow!(
|
|
"Unable to add pem file to certificate store: {}",
|
|
e
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(insecure_allowlist) =
|
|
flags.unsafely_ignore_certificate_errors.as_ref()
|
|
{
|
|
let domains = if insecure_allowlist.is_empty() {
|
|
"for all hostnames".to_string()
|
|
} else {
|
|
format!("for: {}", insecure_allowlist.join(", "))
|
|
};
|
|
let msg =
|
|
format!("DANGER: TLS certificate validation is disabled {}", domains);
|
|
eprintln!("{}", colors::yellow(msg));
|
|
}
|
|
|
|
let cache_usage = if flags.cached_only {
|
|
CacheSetting::Only
|
|
} else if !flags.cache_blocklist.is_empty() {
|
|
CacheSetting::ReloadSome(flags.cache_blocklist.clone())
|
|
} else if flags.reload {
|
|
CacheSetting::ReloadAll
|
|
} else {
|
|
CacheSetting::Use
|
|
};
|
|
|
|
let blob_store = BlobStore::default();
|
|
let broadcast_channel = InMemoryBroadcastChannel::default();
|
|
let shared_array_buffer_store = SharedArrayBufferStore::default();
|
|
let compiled_wasm_module_store = CompiledWasmModuleStore::default();
|
|
|
|
let file_fetcher = FileFetcher::new(
|
|
http_cache,
|
|
cache_usage,
|
|
!flags.no_remote,
|
|
Some(root_cert_store.clone()),
|
|
blob_store.clone(),
|
|
flags.unsafely_ignore_certificate_errors.clone(),
|
|
)?;
|
|
|
|
let lockfile = if let Some(filename) = &flags.lock {
|
|
let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
|
|
Some(Arc::new(Mutex::new(lockfile)))
|
|
} else {
|
|
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<Arc<ImportMap>> =
|
|
match flags.import_map_path.as_ref() {
|
|
None => None,
|
|
Some(import_map_url) => {
|
|
let import_map_specifier =
|
|
deno_core::resolve_url_or_path(import_map_url).context(format!(
|
|
"Bad URL (\"{}\") for import map.",
|
|
import_map_url
|
|
))?;
|
|
let file = file_fetcher
|
|
.fetch(&import_map_specifier, &mut Permissions::allow_all())
|
|
.await
|
|
.context(format!(
|
|
"Unable to load '{}' import map",
|
|
import_map_specifier
|
|
))?;
|
|
let import_map =
|
|
ImportMap::from_json(import_map_specifier.as_str(), &file.source)?;
|
|
Some(Arc::new(import_map))
|
|
}
|
|
};
|
|
|
|
let maybe_inspect_host = flags.inspect.or(flags.inspect_brk);
|
|
let maybe_inspector_server = maybe_inspect_host.map(|host| {
|
|
Arc::new(InspectorServer::new(host, version::get_user_agent()))
|
|
});
|
|
|
|
let coverage_dir = flags
|
|
.coverage_dir
|
|
.clone()
|
|
.or_else(|| env::var("DENO_UNSTABLE_COVERAGE_DIR").ok());
|
|
|
|
// FIXME(bartlomieju): `NodeEsmResolver` is not aware of JSX resolver
|
|
// created below
|
|
let node_resolver = NodeEsmResolver::new(
|
|
maybe_import_map.clone().map(ImportMapResolver::new),
|
|
);
|
|
let maybe_import_map_resolver =
|
|
maybe_import_map.clone().map(ImportMapResolver::new);
|
|
let maybe_jsx_resolver = maybe_config_file
|
|
.as_ref()
|
|
.map(|cf| {
|
|
cf.to_maybe_jsx_import_source_module()
|
|
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
|
|
})
|
|
.flatten();
|
|
let maybe_resolver: Option<
|
|
Arc<dyn deno_graph::source::Resolver + Send + Sync>,
|
|
> = if flags.compat {
|
|
Some(Arc::new(node_resolver))
|
|
} else if let Some(jsx_resolver) = maybe_jsx_resolver {
|
|
// the JSX resolver offloads to the import map if present, otherwise uses
|
|
// the default Deno explicit import resolution.
|
|
Some(Arc::new(jsx_resolver))
|
|
} else if let Some(import_map_resolver) = maybe_import_map_resolver {
|
|
Some(Arc::new(import_map_resolver))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(ProcState(Arc::new(Inner {
|
|
dir,
|
|
coverage_dir,
|
|
flags,
|
|
file_fetcher,
|
|
graph_data: Default::default(),
|
|
lockfile,
|
|
maybe_config_file,
|
|
maybe_import_map,
|
|
maybe_inspector_server,
|
|
root_cert_store: Some(root_cert_store.clone()),
|
|
blob_store,
|
|
broadcast_channel,
|
|
shared_array_buffer_store,
|
|
compiled_wasm_module_store,
|
|
maybe_resolver,
|
|
})))
|
|
}
|
|
|
|
/// Return any imports that should be brought into the scope of the module
|
|
/// graph.
|
|
fn get_maybe_imports(&self) -> MaybeImportsResult {
|
|
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() {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some(imports))
|
|
}
|
|
}
|
|
|
|
/// This method must be called for a module or a static importer of that
|
|
/// module before attempting to `load()` it from a `JsRuntime`. It will
|
|
/// populate `self.graph_data` in memory with the necessary source code or
|
|
/// report any module graph / type checking errors.
|
|
pub(crate) async fn prepare_module_load(
|
|
&self,
|
|
roots: Vec<ModuleSpecifier>,
|
|
is_dynamic: bool,
|
|
lib: emit::TypeLib,
|
|
root_permissions: Permissions,
|
|
dynamic_permissions: Permissions,
|
|
reload_on_watch: bool,
|
|
) -> Result<(), AnyError> {
|
|
// TODO(bartlomieju): this is very make-shift, is there an existing API
|
|
// that we could include it like with "maybe_imports"?
|
|
let roots = if self.flags.compat {
|
|
let mut r = vec![compat::GLOBAL_URL.clone()];
|
|
r.extend(roots);
|
|
r
|
|
} else {
|
|
roots
|
|
};
|
|
if !reload_on_watch {
|
|
let graph_data = self.graph_data.lock();
|
|
if self.flags.check == flags::CheckFlag::None
|
|
|| roots.iter().all(|root| {
|
|
graph_data
|
|
.checked_libs_map
|
|
.get(root)
|
|
.map_or(false, |checked_libs| checked_libs.contains(&lib))
|
|
})
|
|
{
|
|
if let Some(result) = graph_data.check_if_prepared(&roots) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
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: Option<&dyn deno_graph::source::Resolver> =
|
|
if let Some(resolver) = &self.maybe_resolver {
|
|
Some(resolver.as_ref())
|
|
} else {
|
|
None
|
|
};
|
|
let graph = create_graph(
|
|
roots.clone(),
|
|
is_dynamic,
|
|
maybe_imports,
|
|
&mut cache,
|
|
maybe_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 graph_data = self.graph_data.lock();
|
|
graph_data.modules.keys().cloned().collect()
|
|
};
|
|
|
|
let config_type = if self.flags.check == flags::CheckFlag::None {
|
|
emit::ConfigType::Emit
|
|
} else {
|
|
emit::ConfigType::Check {
|
|
tsc_emit: true,
|
|
lib: lib.clone(),
|
|
}
|
|
};
|
|
|
|
let (ts_config, maybe_ignored_options) =
|
|
emit::get_ts_config(config_type, self.maybe_config_file.as_ref(), None)?;
|
|
let graph = Arc::new(graph);
|
|
|
|
let mut type_check_result = Ok(());
|
|
|
|
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.check == flags::CheckFlag::None {
|
|
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| cf.specifier.clone());
|
|
let options = emit::CheckOptions {
|
|
check: self.flags.check.clone(),
|
|
debug: self.flags.log_level == Some(log::Level::Debug),
|
|
emit_with_diagnostics: false,
|
|
maybe_config_specifier,
|
|
ts_config,
|
|
reload: self.flags.reload,
|
|
};
|
|
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 !emit_result.diagnostics.is_empty() {
|
|
type_check_result = Err(anyhow!(emit_result.diagnostics));
|
|
}
|
|
}
|
|
|
|
{
|
|
let mut graph_data = self.graph_data.lock();
|
|
let mut specifiers = graph.specifiers();
|
|
// Set specifier results for redirects.
|
|
// TODO(nayeemrmn): This should be done in `ModuleGraph::specifiers()`.
|
|
for (specifier, found) in &graph.redirects {
|
|
let actual = specifiers.get(found).unwrap().clone();
|
|
specifiers.insert(specifier.clone(), actual);
|
|
}
|
|
for (specifier, result) in &specifiers {
|
|
if let Some(found) = graph.redirects.get(specifier) {
|
|
let module_entry = ModuleEntry::Redirect(found.clone());
|
|
graph_data.modules.insert(specifier.clone(), module_entry);
|
|
continue;
|
|
}
|
|
match result {
|
|
Ok((_, media_type)) => {
|
|
let module = graph.get(specifier).unwrap();
|
|
// If there was a type check error, supply dummy code. It shouldn't
|
|
// be used since preparation will fail.
|
|
let code = if type_check_result.is_err() {
|
|
"".to_string()
|
|
// Check to see if there is an emitted file in the cache.
|
|
} else if let Some(code) =
|
|
cache.get(cache::CacheType::Emit, specifier)
|
|
{
|
|
code
|
|
// Then if the file is JavaScript (or unknown) and wasn't emitted,
|
|
// we will load the original source code in the module.
|
|
} else if matches!(
|
|
media_type,
|
|
MediaType::JavaScript
|
|
| MediaType::Unknown
|
|
| MediaType::Cjs
|
|
| MediaType::Mjs
|
|
| MediaType::Json
|
|
) {
|
|
module.maybe_source().unwrap_or("").to_string()
|
|
// The emit may also be missing when a declaration file is in the
|
|
// graph. There shouldn't be any runtime statements in the source
|
|
// file and if there was, users would be shown a `TS1036`
|
|
// diagnostic. So just return an empty emit.
|
|
} else if !emit::is_emittable(media_type, true) {
|
|
"".to_string()
|
|
} else {
|
|
unreachable!(
|
|
"unexpected missing emit: {} media type: {}",
|
|
specifier, media_type
|
|
)
|
|
};
|
|
let dependencies =
|
|
module.maybe_dependencies().cloned().unwrap_or_default();
|
|
graph_data.modules.insert(
|
|
specifier.clone(),
|
|
ModuleEntry::Module {
|
|
code,
|
|
dependencies,
|
|
media_type: *media_type,
|
|
},
|
|
);
|
|
if let Some(dependencies) = module.maybe_dependencies() {
|
|
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 =
|
|
graph_data.referrer_map.entry(specifier.clone());
|
|
entry.or_insert_with(|| referrer_range.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(error) => {
|
|
let module_entry = ModuleEntry::Error(error.clone());
|
|
graph_data.modules.insert(specifier.clone(), module_entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
graph_data.check_if_prepared(&roots).unwrap()?;
|
|
type_check_result?;
|
|
|
|
if self.flags.check != flags::CheckFlag::None {
|
|
for specifier in specifiers.keys() {
|
|
let checked_libs = graph_data
|
|
.checked_libs_map
|
|
.entry(specifier.clone())
|
|
.or_default();
|
|
checked_libs.insert(lib.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
// any updates to the lockfile should be updated now
|
|
if let Some(ref lockfile) = self.lockfile {
|
|
let g = lockfile.lock();
|
|
g.write()?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn resolve(
|
|
&self,
|
|
specifier: &str,
|
|
referrer: &str,
|
|
) -> Result<ModuleSpecifier, AnyError> {
|
|
if let Ok(referrer) = deno_core::resolve_url_or_path(referrer) {
|
|
let graph_data = self.graph_data.lock();
|
|
let found_referrer = match graph_data.modules.get(&referrer) {
|
|
Some(ModuleEntry::Redirect(r)) => r,
|
|
_ => &referrer,
|
|
};
|
|
let maybe_resolved = match graph_data.modules.get(found_referrer) {
|
|
Some(ModuleEntry::Module { dependencies, .. }) => dependencies
|
|
.get(specifier)
|
|
.and_then(|dep| dep.maybe_code.clone()),
|
|
_ => None,
|
|
};
|
|
|
|
match maybe_resolved {
|
|
Some(Ok((specifier, _))) => return Ok(specifier),
|
|
Some(Err(err)) => {
|
|
return Err(custom_error(
|
|
"TypeError",
|
|
format!("{}\n", err.to_string_with_range()),
|
|
))
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
|
|
// FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL
|
|
// and `Deno.core.evalContext` API. Ideally we should always have a referrer filled
|
|
// but sadly that's not the case due to missing APIs in V8.
|
|
let referrer = if referrer.is_empty() && self.flags.repl {
|
|
deno_core::resolve_url_or_path("./$deno$repl.ts").unwrap()
|
|
} else {
|
|
deno_core::resolve_url_or_path(referrer).unwrap()
|
|
};
|
|
|
|
let maybe_resolver: Option<&dyn deno_graph::source::Resolver> =
|
|
if let Some(resolver) = &self.maybe_resolver {
|
|
Some(resolver.as_ref())
|
|
} else {
|
|
None
|
|
};
|
|
if let Some(resolver) = &maybe_resolver {
|
|
resolver.resolve(specifier, &referrer)
|
|
} else {
|
|
deno_core::resolve_import(specifier, referrer.as_str())
|
|
.map_err(|err| err.into())
|
|
}
|
|
}
|
|
|
|
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 graph_data = self.graph_data.lock();
|
|
let found_specifier = match graph_data.modules.get(&specifier) {
|
|
Some(ModuleEntry::Redirect(s)) => s,
|
|
_ => &specifier,
|
|
};
|
|
match graph_data.modules.get(found_specifier) {
|
|
Some(ModuleEntry::Module {
|
|
code, media_type, ..
|
|
}) => Ok(ModuleSource {
|
|
code: code.clone(),
|
|
module_url_specified: specifier.to_string(),
|
|
module_url_found: found_specifier.to_string(),
|
|
module_type: match media_type {
|
|
MediaType::Json => ModuleType::Json,
|
|
_ => ModuleType::JavaScript,
|
|
},
|
|
}),
|
|
_ => Err(anyhow!(
|
|
"Loading unprepared module: {}",
|
|
specifier.to_string()
|
|
)),
|
|
}
|
|
}
|
|
|
|
// TODO(@kitsonk) this should be refactored to get it from the module graph
|
|
fn get_emit(&self, url: &Url) -> Option<(Vec<u8>, Option<Vec<u8>>)> {
|
|
let emit_path = self
|
|
.dir
|
|
.gen_cache
|
|
.get_cache_filename_with_extension(url, "js")?;
|
|
let emit_map_path = self
|
|
.dir
|
|
.gen_cache
|
|
.get_cache_filename_with_extension(url, "js.map")?;
|
|
if let Ok(code) = self.dir.gen_cache.get(&emit_path) {
|
|
let maybe_map = if let Ok(map) = self.dir.gen_cache.get(&emit_map_path) {
|
|
Some(map)
|
|
} else {
|
|
None
|
|
};
|
|
Some((code, maybe_map))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(@kitsonk) this is only temporary, but should be refactored to somewhere
|
|
// else, like a refactored file_fetcher.
|
|
impl SourceMapGetter for ProcState {
|
|
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
|
|
if let Ok(specifier) = resolve_url(file_name) {
|
|
match specifier.scheme() {
|
|
// we should only be looking for emits for schemes that denote external
|
|
// modules, which the disk_cache supports
|
|
"wasm" | "file" | "http" | "https" | "data" | "blob" => (),
|
|
_ => return None,
|
|
}
|
|
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, false) {
|
|
source_map_from_code(source.code)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_source_line(
|
|
&self,
|
|
file_name: &str,
|
|
line_number: usize,
|
|
) -> Option<String> {
|
|
if let Ok(specifier) = resolve_url(file_name) {
|
|
self.file_fetcher.get_source(&specifier).map(|out| {
|
|
// Do NOT use .lines(): it skips the terminating empty line.
|
|
// (due to internally using .split_terminator() instead of .split())
|
|
let lines: Vec<&str> = out.source.split('\n').collect();
|
|
if line_number >= lines.len() {
|
|
format!(
|
|
"{} Couldn't format source line: Line {} is out of bounds (source may have changed at runtime)",
|
|
crate::colors::yellow("Warning"), line_number + 1,
|
|
)
|
|
} else {
|
|
lines[line_number].to_string()
|
|
}
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn source_map_from_code(code: String) -> Option<Vec<u8>> {
|
|
let lines: Vec<&str> = code.split('\n').collect();
|
|
if let Some(last_line) = lines.last() {
|
|
if last_line
|
|
.starts_with("//# sourceMappingURL=data:application/json;base64,")
|
|
{
|
|
let input = last_line.trim_start_matches(
|
|
"//# sourceMappingURL=data:application/json;base64,",
|
|
);
|
|
let decoded_map = base64::decode(input)
|
|
.expect("Unable to decode source map from emitted file.");
|
|
Some(decoded_map)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|