1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-28 16:20:57 -05:00

refactor: integrate deno_graph into CLI (#12369)

This commit is contained in:
Kitson Kelly 2021-10-11 08:26:22 +11:00 committed by GitHub
parent 5a8a989b78
commit a7baf5f2bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 2970 additions and 5116 deletions

411
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -41,8 +41,8 @@ winres = "0.1.11"
[dependencies] [dependencies]
deno_ast = { version = "0.2.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_ast = { version = "0.2.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { version = "0.102.0", path = "../core" } deno_core = { version = "0.102.0", path = "../core" }
deno_doc = "0.14.0" deno_doc = "0.15.0"
deno_graph = "0.5.0" deno_graph = "0.6.0"
deno_lint = { version = "0.16.0", features = ["docs"] } deno_lint = { version = "0.16.0", features = ["docs"] }
deno_runtime = { version = "0.28.0", path = "../runtime" } deno_runtime = { version = "0.28.0", path = "../runtime" }
deno_tls = { version = "0.7.0", path = "../ext/tls" } deno_tls = { version = "0.7.0", path = "../ext/tls" }

View file

@ -4,6 +4,7 @@ use deno_ast::swc::bundler::ModuleRecord;
use deno_ast::swc::common::Span; use deno_ast::swc::common::Span;
use deno_core::error::AnyError; use deno_core::error::AnyError;
/// This contains the logic for Deno to rewrite the `import.meta` when bundling.
pub struct BundleHook; pub struct BundleHook;
impl Hook for BundleHook { impl Hook for BundleHook {

349
cli/cache.rs Normal file
View file

@ -0,0 +1,349 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::disk_cache::DiskCache;
use crate::file_fetcher::FileFetcher;
use deno_core::error::AnyError;
use deno_core::futures::future;
use deno_core::futures::FutureExt;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json;
use deno_core::ModuleSpecifier;
use deno_graph::source::CacheInfo;
use deno_graph::source::LoadFuture;
use deno_graph::source::LoadResponse;
use deno_graph::source::Loader;
use deno_runtime::permissions::Permissions;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Deserialize, Serialize)]
pub struct EmitMetadata {
pub version_hash: String,
}
pub(crate) enum CacheType {
Declaration,
Emit,
SourceMap,
TypeScriptBuildInfo,
Version,
}
/// A trait which provides a concise implementation to getting and setting
/// values in a cache.
pub(crate) trait Cacher {
/// Get a value from the cache.
fn get(
&self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
) -> Option<String>;
/// Set a value in the cache.
fn set(
&mut self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
value: String,
) -> Result<(), AnyError>;
}
/// Combines the cacher trait along with the deno_graph Loader trait to provide
/// a single interface to be able to load and cache modules when building a
/// graph.
pub(crate) trait CacherLoader: Cacher + Loader {
fn as_cacher(&self) -> &dyn Cacher;
fn as_mut_loader(&mut self) -> &mut dyn Loader;
fn as_mut_cacher(&mut self) -> &mut dyn Cacher;
}
/// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides
/// a concise interface to the DENO_DIR when building module graphs.
pub(crate) struct FetchCacher {
disk_cache: DiskCache,
dynamic_permissions: Permissions,
file_fetcher: Arc<FileFetcher>,
root_permissions: Permissions,
}
impl FetchCacher {
pub fn new(
disk_cache: DiskCache,
file_fetcher: FileFetcher,
root_permissions: Permissions,
dynamic_permissions: Permissions,
) -> Self {
let file_fetcher = Arc::new(file_fetcher);
Self {
disk_cache,
dynamic_permissions,
file_fetcher,
root_permissions,
}
}
fn get_emit_metadata(
&self,
specifier: &ModuleSpecifier,
) -> Option<EmitMetadata> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "meta")?;
let bytes = self.disk_cache.get(&filename).ok()?;
serde_json::from_slice(&bytes).ok()
}
fn set_emit_metadata(
&self,
specifier: &ModuleSpecifier,
data: EmitMetadata,
) -> Result<(), AnyError> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "meta")
.unwrap();
let bytes = serde_json::to_vec(&data)?;
self.disk_cache.set(&filename, &bytes).map_err(|e| e.into())
}
}
impl Loader for FetchCacher {
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
let local = self.file_fetcher.get_local_path(specifier)?;
if local.is_file() {
let location = &self.disk_cache.location;
let emit = self
.disk_cache
.get_cache_filename_with_extension(specifier, "js")
.map(|p| location.join(p))
.filter(|p| p.is_file());
let map = self
.disk_cache
.get_cache_filename_with_extension(specifier, "js.map")
.map(|p| location.join(p))
.filter(|p| p.is_file());
Some(CacheInfo {
local: Some(local),
emit,
map,
})
} else {
None
}
}
fn load(
&mut self,
specifier: &ModuleSpecifier,
is_dynamic: bool,
) -> LoadFuture {
let specifier = specifier.clone();
let mut permissions = if is_dynamic {
self.dynamic_permissions.clone()
} else {
self.root_permissions.clone()
};
let file_fetcher = self.file_fetcher.clone();
async move {
let load_result = file_fetcher
.fetch(&specifier, &mut permissions)
.await
.map_or_else(
|err| {
if let Some(err) = err.downcast_ref::<std::io::Error>() {
if err.kind() == std::io::ErrorKind::NotFound {
return Ok(None);
}
}
Err(err)
},
|file| {
Ok(Some(LoadResponse {
specifier: file.specifier,
maybe_headers: file.maybe_headers,
content: file.source,
}))
},
);
(specifier, load_result)
}
.boxed()
}
}
impl Cacher for FetchCacher {
fn get(
&self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
) -> Option<String> {
let extension = match cache_type {
CacheType::Declaration => "d.ts",
CacheType::Emit => "js",
CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => {
return self.get_emit_metadata(specifier).map(|d| d.version_hash)
}
};
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, extension)?;
self
.disk_cache
.get(&filename)
.ok()
.map(|b| String::from_utf8(b).ok())
.flatten()
}
fn set(
&mut self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
value: String,
) -> Result<(), AnyError> {
let extension = match cache_type {
CacheType::Declaration => "d.ts",
CacheType::Emit => "js",
CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => {
let data = if let Some(mut data) = self.get_emit_metadata(specifier) {
data.version_hash = value;
data
} else {
EmitMetadata {
version_hash: value,
}
};
return self.set_emit_metadata(specifier, data);
}
};
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, extension)
.unwrap();
self
.disk_cache
.set(&filename, value.as_bytes())
.map_err(|e| e.into())
}
}
impl CacherLoader for FetchCacher {
fn as_cacher(&self) -> &dyn Cacher {
self
}
fn as_mut_loader(&mut self) -> &mut dyn Loader {
self
}
fn as_mut_cacher(&mut self) -> &mut dyn Cacher {
self
}
}
/// An in memory cache that is used by the runtime `Deno.emit()` API to provide
/// the same behavior as the disk cache when sources are provided.
#[derive(Debug)]
pub(crate) struct MemoryCacher {
sources: HashMap<String, Arc<String>>,
declarations: HashMap<ModuleSpecifier, String>,
emits: HashMap<ModuleSpecifier, String>,
maps: HashMap<ModuleSpecifier, String>,
build_infos: HashMap<ModuleSpecifier, String>,
versions: HashMap<ModuleSpecifier, String>,
}
impl MemoryCacher {
pub fn new(sources: HashMap<String, Arc<String>>) -> Self {
Self {
sources,
declarations: HashMap::default(),
emits: HashMap::default(),
maps: HashMap::default(),
build_infos: HashMap::default(),
versions: HashMap::default(),
}
}
}
impl Loader for MemoryCacher {
fn load(
&mut self,
specifier: &ModuleSpecifier,
_is_dynamic: bool,
) -> LoadFuture {
let mut specifier_str = specifier.to_string();
if !self.sources.contains_key(&specifier_str) {
specifier_str = specifier_str.replace("file:///", "/");
if !self.sources.contains_key(&specifier_str) {
specifier_str = specifier_str[3..].to_string();
}
}
let response = self.sources.get(&specifier_str).map(|c| LoadResponse {
specifier: specifier.clone(),
maybe_headers: None,
content: c.to_owned(),
});
Box::pin(future::ready((specifier.clone(), Ok(response))))
}
}
impl Cacher for MemoryCacher {
fn get(
&self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
) -> Option<String> {
match cache_type {
CacheType::Declaration => self.declarations.get(specifier).cloned(),
CacheType::Emit => self.emits.get(specifier).cloned(),
CacheType::SourceMap => self.maps.get(specifier).cloned(),
CacheType::TypeScriptBuildInfo => {
self.build_infos.get(specifier).cloned()
}
CacheType::Version => self.versions.get(specifier).cloned(),
}
}
fn set(
&mut self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
value: String,
) -> Result<(), AnyError> {
match cache_type {
CacheType::Declaration => {
self.declarations.insert(specifier.clone(), value)
}
CacheType::Emit => self.emits.insert(specifier.clone(), value),
CacheType::SourceMap => self.maps.insert(specifier.clone(), value),
CacheType::TypeScriptBuildInfo => {
self.build_infos.insert(specifier.clone(), value)
}
CacheType::Version => self.versions.insert(specifier.clone(), value),
};
Ok(())
}
}
impl CacherLoader for MemoryCacher {
fn as_cacher(&self) -> &dyn Cacher {
self
}
fn as_mut_loader(&mut self) -> &mut dyn Loader {
self
}
fn as_mut_cacher(&mut self) -> &mut dyn Cacher {
self
}
}

View file

@ -4,6 +4,7 @@ use deno_core::url::Url;
use std::collections::HashMap; use std::collections::HashMap;
static STD_NODE: &str = "https://deno.land/std/node/"; static STD_NODE: &str = "https://deno.land/std/node/";
static GLOBAL_MODULE: &str = "global.ts";
static SUPPORTED_MODULES: &[&str] = &[ static SUPPORTED_MODULES: &[&str] = &[
"assert", "assert",
@ -50,8 +51,15 @@ static SUPPORTED_MODULES: &[&str] = &[
"zlib", "zlib",
]; ];
pub fn get_node_globals_url() -> Url { lazy_static::lazy_static! {
Url::parse(&format!("{}global.ts", STD_NODE)).unwrap() static ref GLOBAL_URL_STR: String = format!("{}{}", STD_NODE, GLOBAL_MODULE);
pub(crate) static ref GLOBAL_URL: Url = Url::parse(&GLOBAL_URL_STR).unwrap();
static ref COMPAT_IMPORT_URL: Url = Url::parse("flags:compat").unwrap();
}
/// Provide imports into a module graph when the compat flag is true.
pub(crate) fn get_node_imports() -> Vec<(Url, Vec<String>)> {
vec![(COMPAT_IMPORT_URL.clone(), vec![GLOBAL_URL_STR.clone()])]
} }
/// Create a map that can be used to update import map. /// Create a map that can be used to update import map.

View file

@ -10,6 +10,7 @@ use deno_core::serde::Serializer;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
@ -401,6 +402,19 @@ impl ConfigFile {
} }
} }
/// If the configuration file contains "extra" modules (like TypeScript
/// `"types"`) options, return them as imports to be added to a module graph.
pub fn to_maybe_imports(
&self,
) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
let compiler_options_value = self.json.compiler_options.as_ref()?;
let compiler_options: CompilerOptions =
serde_json::from_value(compiler_options_value.clone()).ok()?;
let referrer = ModuleSpecifier::from_file_path(&self.path).ok()?;
let types = compiler_options.types?;
Some(vec![(referrer, types)])
}
pub fn to_fmt_config(&self) -> Result<Option<FmtConfig>, AnyError> { pub fn to_fmt_config(&self) -> Result<Option<FmtConfig>, AnyError> {
if let Some(config) = self.json.fmt.clone() { if let Some(config) = self.json.fmt.clone() {
let fmt_config: FmtConfig = serde_json::from_value(config) let fmt_config: FmtConfig = serde_json::from_value(config)

View file

@ -6,9 +6,8 @@ use deno_core::serde::Deserialize;
use deno_core::serde::Deserializer; use deno_core::serde::Deserializer;
use deno_core::serde::Serialize; use deno_core::serde::Serialize;
use deno_core::serde::Serializer; use deno_core::serde::Serializer;
use deno_core::ModuleSpecifier; use deno_graph::ModuleGraphError;
use regex::Regex; use regex::Regex;
use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
@ -353,20 +352,17 @@ impl Diagnostics {
Diagnostics(diagnostics) Diagnostics(diagnostics)
} }
pub fn extend_graph_errors( pub fn extend_graph_errors(&mut self, errors: Vec<ModuleGraphError>) {
&mut self, self.0.extend(errors.into_iter().map(|err| Diagnostic {
errors: HashMap<ModuleSpecifier, String>,
) {
self.0.extend(errors.into_iter().map(|(s, e)| Diagnostic {
category: DiagnosticCategory::Error, category: DiagnosticCategory::Error,
code: 900001, code: 900001,
start: None, start: None,
end: None, end: None,
message_text: Some(e), message_text: Some(err.to_string()),
message_chain: None, message_chain: None,
source: None, source: None,
source_line: None, source_line: None,
file_name: Some(s.to_string()), file_name: Some(err.specifier().to_string()),
related_information: None, related_information: None,
})); }));
} }

927
cli/emit.rs Normal file
View file

@ -0,0 +1,927 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
//! The collection of APIs to be able to take `deno_graph` module graphs and
//! populate a cache, emit files, and transform a graph into the structures for
//! loading into an isolate.
use crate::ast;
use crate::cache::CacheType;
use crate::cache::Cacher;
use crate::colors;
use crate::config_file::ConfigFile;
use crate::config_file::IgnoredCompilerOptions;
use crate::config_file::TsConfig;
use crate::diagnostics::Diagnostics;
use crate::tsc;
use crate::version;
use deno_ast::swc;
use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::error::Context;
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::json;
use deno_core::serde_json::Value;
use deno_core::ModuleSource;
use deno_core::ModuleSpecifier;
use deno_graph::MediaType;
use deno_graph::ModuleGraph;
use deno_graph::ModuleGraphError;
use deno_graph::ResolutionError;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt;
use std::rc::Rc;
use std::result;
use std::sync::Arc;
use std::time::Instant;
/// 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.
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum TypeLib {
DenoWindow,
DenoWorker,
UnstableDenoWindow,
UnstableDenoWorker,
}
impl Default for TypeLib {
fn default() -> Self {
Self::DenoWindow
}
}
impl Serialize for TypeLib {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = match self {
Self::DenoWindow => vec!["deno.window".to_string()],
Self::DenoWorker => vec!["deno.worker".to_string()],
Self::UnstableDenoWindow => {
vec!["deno.window".to_string(), "deno.unstable".to_string()]
}
Self::UnstableDenoWorker => {
vec!["deno.worker".to_string(), "deno.unstable".to_string()]
}
};
Serialize::serialize(&value, serializer)
}
}
type Modules = HashMap<ModuleSpecifier, Result<ModuleSource, AnyError>>;
/// A structure representing stats from an emit operation for a graph.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct Stats(pub Vec<(String, u32)>);
impl<'de> Deserialize<'de> for Stats {
fn deserialize<D>(deserializer: D) -> result::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(())
}
}
/// An enum that represents the base tsc configuration to return.
pub(crate) enum ConfigType {
/// Return a configuration for bundling, using swc to emit the bundle. This is
/// independent of type checking.
Bundle,
/// Return a configuration to use tsc to type check and optionally emit. This
/// is independent of either bundling or just emitting via swc
Check { lib: TypeLib, tsc_emit: bool },
/// Return a configuration to use swc to emit single module files.
Emit,
/// Return a configuration as a base for the runtime `Deno.emit()` API.
RuntimeEmit { tsc_emit: bool },
}
/// For a given configuration type and optionally a configuration file, return a
/// tuple of the resulting `TsConfig` struct and optionally any user
/// configuration options that were ignored.
pub(crate) fn get_ts_config(
config_type: ConfigType,
maybe_config_file: Option<&ConfigFile>,
maybe_user_config: Option<&HashMap<String, Value>>,
) -> Result<(TsConfig, Option<IgnoredCompilerOptions>), AnyError> {
let mut ts_config = match config_type {
ConfigType::Bundle => TsConfig::new(json!({
"checkJs": false,
"emitDecoratorMetadata": false,
"importsNotUsedAsValues": "remove",
"inlineSourceMap": false,
"inlineSources": false,
"sourceMap": false,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
})),
ConfigType::Check { tsc_emit, lib } => {
let mut ts_config = TsConfig::new(json!({
"allowJs": true,
"experimentalDecorators": true,
"incremental": true,
"jsx": "react",
"isolatedModules": true,
"lib": lib,
"module": "esnext",
"strict": true,
"target": "esnext",
"tsBuildInfoFile": "deno:///.tsbuildinfo",
"useDefineForClassFields": true,
// TODO(@kitsonk) remove for Deno 2.0
"useUnknownInCatchVariables": false,
}));
if tsc_emit {
ts_config.merge(&json!({
"emitDecoratorMetadata": false,
"importsNotUsedAsValues": "remove",
"inlineSourceMap": true,
"inlineSources": true,
"outDir": "deno://",
"removeComments": true,
}));
} else {
ts_config.merge(&json!({
"noEmit": true,
}));
}
ts_config
}
ConfigType::Emit => TsConfig::new(json!({
"checkJs": false,
"emitDecoratorMetadata": false,
"importsNotUsedAsValues": "remove",
"inlineSourceMap": true,
// TODO(@kitsonk) make this actually work when https://github.com/swc-project/swc/issues/2218 addressed.
"inlineSources": true,
"sourceMap": false,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
})),
ConfigType::RuntimeEmit { tsc_emit } => {
let mut ts_config = TsConfig::new(json!({
"allowJs": true,
"checkJs": false,
"emitDecoratorMetadata": false,
"experimentalDecorators": true,
"importsNotUsedAsValues": "remove",
"incremental": true,
"isolatedModules": true,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"lib": TypeLib::DenoWindow,
"module": "esnext",
"removeComments": true,
"inlineSourceMap": false,
"inlineSources": false,
"sourceMap": true,
"strict": true,
"target": "esnext",
"tsBuildInfoFile": "deno:///.tsbuildinfo",
"useDefineForClassFields": true,
// TODO(@kitsonk) remove for Deno 2.0
"useUnknownInCatchVariables": false,
}));
if tsc_emit {
ts_config.merge(&json!({
"importsNotUsedAsValues": "remove",
"outDir": "deno://",
}));
} else {
ts_config.merge(&json!({
"noEmit": true,
}));
}
ts_config
}
};
let maybe_ignored_options = if let Some(user_options) = maybe_user_config {
ts_config.merge_user_config(user_options)?
} else {
ts_config.merge_tsconfig_from_config_file(maybe_config_file)?
};
Ok((ts_config, maybe_ignored_options))
}
/// 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. If we aren't checking JavaScript, 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_root_names(
graph: &ModuleGraph,
check_js: bool,
) -> Vec<(ModuleSpecifier, MediaType)> {
if !check_js {
graph
.specifiers()
.into_iter()
.filter_map(|(_, r)| match r {
Ok((s, mt)) => match &mt {
MediaType::TypeScript | MediaType::Tsx | MediaType::Jsx => {
Some((s, mt))
}
_ => None,
},
_ => None,
})
.collect()
} else {
graph
.roots
.iter()
.filter_map(|s| graph.get(s).map(|m| (m.specifier.clone(), m.media_type)))
.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.
fn get_version(source_bytes: &[u8], config_bytes: &[u8]) -> String {
crate::checksum::gen(&[
source_bytes,
version::deno().as_bytes(),
config_bytes,
])
}
/// Determine if a given media type is emittable or not.
fn is_emittable(media_type: &MediaType, include_js: bool) -> bool {
match &media_type {
MediaType::TypeScript | MediaType::Tsx | MediaType::Jsx => true,
MediaType::JavaScript => include_js,
_ => false,
}
}
/// 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(crate) struct CheckOptions {
/// Set the debug flag on the TypeScript type checker.
pub debug: bool,
/// If true, any files emitted will be cached, even if there are diagnostics
/// produced. If false, if there are diagnostics, caching emitted files will
/// be skipped.
pub emit_with_diagnostics: 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,
}
/// The result of a check or emit of a module graph. Note that the actual
/// emitted sources are stored in the cache and are not returned in the result.
#[derive(Debug, Default)]
pub(crate) struct CheckEmitResult {
pub diagnostics: Diagnostics,
pub stats: Stats,
}
/// Given a module graph, type check the module graph and optionally emit
/// modules, updating the cache as appropriate. Emitting is determined by the
/// `ts_config` supplied in the options, and if emitting, the files are stored
/// in the cache.
///
/// It is expected that it is determined if a check and/or emit is validated
/// before the function is called.
pub(crate) fn check_and_maybe_emit(
graph: Arc<ModuleGraph>,
cache: &mut dyn Cacher,
options: CheckOptions,
) -> Result<CheckEmitResult, AnyError> {
let check_js = options.ts_config.get_check_js();
let root_names = get_root_names(&graph, check_js);
// 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 =
cache.get(CacheType::TypeScriptBuildInfo, &graph.roots[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 config_bytes = options.ts_config.as_bytes();
let response = tsc::exec(tsc::Request {
config: options.ts_config,
debug: options.debug,
graph: graph.clone(),
hash_data,
maybe_config_specifier: options.maybe_config_specifier,
maybe_tsbuildinfo,
root_names,
})?;
if let Some(info) = &response.maybe_tsbuildinfo {
// while we retrieve the build info for just the first module, it can be
// used for all the roots in the graph, so we will cache it for all roots
for root in &graph.roots {
cache.set(CacheType::TypeScriptBuildInfo, root, info.clone())?;
}
}
// sometimes we want to emit when there are diagnostics, and sometimes we
// don't. tsc will always return an emit if there are diagnostics
if (response.diagnostics.is_empty() || options.emit_with_diagnostics)
&& !response.emitted_files.is_empty()
{
for emit in response.emitted_files.into_iter() {
if let Some(specifiers) = emit.maybe_specifiers {
assert!(specifiers.len() == 1);
// The emitted specifier might not be the file specifier we want, so we
// resolve it via the graph.
let specifier = graph.resolve(&specifiers[0]);
let (media_type, source) = if let Some(module) = graph.get(&specifier) {
(&module.media_type, module.source.clone())
} else {
log::debug!("module missing, skipping emit for {}", specifier);
continue;
};
// Sometimes if `tsc` sees a CommonJS file it will _helpfully_ output it
// to ESM, which we don't really want to do unless someone has enabled
// check_js.
if !check_js && *media_type == MediaType::JavaScript {
log::debug!("skipping emit for {}", specifier);
continue;
}
match emit.media_type {
MediaType::JavaScript => {
let version = get_version(source.as_bytes(), &config_bytes);
cache.set(CacheType::Version, &specifier, version)?;
cache.set(CacheType::Emit, &specifier, emit.data)?;
}
MediaType::SourceMap => {
cache.set(CacheType::SourceMap, &specifier, emit.data)?;
}
// this only occurs with the runtime emit, but we are using the same
// code paths, so we handle it here.
MediaType::Dts => {
cache.set(CacheType::Declaration, &specifier, emit.data)?;
}
_ => unreachable!(),
}
}
}
}
Ok(CheckEmitResult {
diagnostics: response.diagnostics,
stats: response.stats,
})
}
pub(crate) enum BundleType {
/// Return the emitted contents of the program as a single "flattened" ES
/// module.
Module,
/// Return the emitted contents of the program as a single script that
/// executes the program using an immediately invoked function execution
/// (IIFE).
Classic,
}
impl From<BundleType> for swc::bundler::ModuleType {
fn from(bundle_type: BundleType) -> Self {
match bundle_type {
BundleType::Classic => Self::Iife,
BundleType::Module => Self::Es,
}
}
}
pub(crate) struct BundleOptions {
pub bundle_type: BundleType,
pub ts_config: TsConfig,
}
/// A module loader for swc which does the appropriate retrieval and transpiling
/// of modules from the graph.
struct BundleLoader<'a> {
cm: Rc<swc::common::SourceMap>,
emit_options: &'a ast::EmitOptions,
globals: &'a deno_ast::swc::common::Globals,
graph: &'a ModuleGraph,
}
impl swc::bundler::Load for BundleLoader<'_> {
fn load(
&self,
file_name: &swc::common::FileName,
) -> Result<swc::bundler::ModuleData, AnyError> {
match file_name {
swc::common::FileName::Url(specifier) => {
if let Some(m) = self.graph.get(specifier) {
let (fm, module) = ast::transpile_module(
specifier,
&m.source,
m.media_type,
self.emit_options,
self.globals,
self.cm.clone(),
)?;
Ok(swc::bundler::ModuleData {
fm,
module,
helpers: Default::default(),
})
} else {
Err(anyhow!(
"Module \"{}\" unexpectedly missing when bundling.",
specifier
))
}
}
_ => unreachable!(
"Received a request for unsupported filename {:?}",
file_name
),
}
}
}
/// A resolver implementation for swc that resolves specifiers from the graph.
struct BundleResolver<'a>(&'a ModuleGraph);
impl swc::bundler::Resolve for BundleResolver<'_> {
fn resolve(
&self,
referrer: &swc::common::FileName,
specifier: &str,
) -> Result<swc::common::FileName, AnyError> {
let referrer = if let swc::common::FileName::Url(referrer) = referrer {
referrer
} else {
unreachable!(
"An unexpected referrer was passed when bundling: {:?}",
referrer
);
};
if let Some(specifier) =
self.0.resolve_dependency(specifier, referrer, false)
{
Ok(deno_ast::swc::common::FileName::Url(specifier.clone()))
} else {
Err(anyhow!(
"Cannot resolve \"{}\" from \"{}\".",
specifier,
referrer
))
}
}
}
/// Given a module graph, generate and return a bundle of the graph and
/// optionally its source map. Unlike emitting with `check_and_maybe_emit` and
/// `emit`, which store the emitted modules in the cache, this function simply
/// returns the output.
pub(crate) fn bundle(
graph: &ModuleGraph,
options: BundleOptions,
) -> Result<(String, Option<String>), AnyError> {
let emit_options: ast::EmitOptions = options.ts_config.into();
let cm = Rc::new(swc::common::SourceMap::new(
swc::common::FilePathMapping::empty(),
));
let globals = swc::common::Globals::new();
let loader = BundleLoader {
graph,
emit_options: &emit_options,
globals: &globals,
cm: cm.clone(),
};
let resolver = BundleResolver(graph);
let config = swc::bundler::Config {
module: options.bundle_type.into(),
..Default::default()
};
// This hook will rewrite the `import.meta` when bundling to give a consistent
// behavior between bundled and unbundled code.
let hook = Box::new(ast::BundleHook);
let bundler = swc::bundler::Bundler::new(
&globals,
cm.clone(),
loader,
resolver,
config,
hook,
);
let mut entries = HashMap::new();
entries.insert(
"bundle".to_string(),
swc::common::FileName::Url(graph.roots[0].clone()),
);
let output = bundler
.bundle(entries)
.context("Unable to output during bundling.")?;
let mut buf = Vec::new();
let mut srcmap = Vec::new();
{
let cfg = swc::codegen::Config { minify: false };
let wr = Box::new(swc::codegen::text_writer::JsWriter::new(
cm.clone(),
"\n",
&mut buf,
Some(&mut srcmap),
));
let mut emitter = swc::codegen::Emitter {
cfg,
cm: cm.clone(),
comments: None,
wr,
};
emitter
.emit_module(&output[0].module)
.context("Unable to emit during bundling.")?;
}
let mut code =
String::from_utf8(buf).context("Emitted code is an invalid string.")?;
let mut maybe_map: Option<String> = None;
{
let mut buf = Vec::new();
cm.build_source_map_from(&mut srcmap, None)
.to_writer(&mut buf)?;
if emit_options.inline_source_map {
let encoded_map = format!(
"//# sourceMappingURL=data:application/json;base64,{}\n",
base64::encode(buf)
);
code.push_str(&encoded_map);
} else if emit_options.source_map {
maybe_map = Some(String::from_utf8(buf)?);
}
}
Ok((code, maybe_map))
}
pub(crate) struct EmitOptions {
pub ts_config: TsConfig,
pub reload_exclusions: HashSet<ModuleSpecifier>,
pub reload: bool,
}
/// Given a module graph, emit any appropriate modules and cache them.
pub(crate) fn emit(
graph: &ModuleGraph,
cache: &mut dyn Cacher,
options: EmitOptions,
) -> Result<CheckEmitResult, AnyError> {
let start = Instant::now();
let config_bytes = options.ts_config.as_bytes();
let include_js = options.ts_config.get_check_js();
let emit_options = options.ts_config.into();
let mut emit_count = 0_u32;
let mut file_count = 0_u32;
for module in graph.modules() {
file_count += 1;
if !is_emittable(&module.media_type, include_js) {
continue;
}
let needs_reload =
options.reload && !options.reload_exclusions.contains(&module.specifier);
let version = get_version(module.source.as_bytes(), &config_bytes);
let is_valid = cache
.get(CacheType::Version, &module.specifier)
.map_or(false, |v| {
v == get_version(module.source.as_bytes(), &config_bytes)
});
if is_valid && !needs_reload {
continue;
}
let (emit, maybe_map) =
ast::transpile(&module.parsed_source, &emit_options)?;
emit_count += 1;
cache.set(CacheType::Emit, &module.specifier, emit)?;
if let Some(map) = maybe_map {
cache.set(CacheType::SourceMap, &module.specifier, map)?;
}
if !is_valid {
cache.set(CacheType::Version, &module.specifier, version)?;
}
}
let stats = Stats(vec![
("Files".to_string(), file_count),
("Emitted".to_string(), emit_count),
("Total time".to_string(), start.elapsed().as_millis() as u32),
]);
Ok(CheckEmitResult {
diagnostics: Diagnostics::default(),
stats,
})
}
/// Check the sub-resource integrity of a module graph, exiting if the graph is
/// not valid.
pub(crate) fn lock(graph: &ModuleGraph) {
if let Err(err) = graph.lock() {
log::error!("{} {}", colors::red("error:"), err);
std::process::exit(10);
}
}
/// Check a module graph to determine if the graph contains anything that
/// is required to be emitted to be valid. It determines what modules in the
/// graph are emittable and for those that are emittable, if there is currently
/// a valid emit in the cache.
pub(crate) fn valid_emit(
graph: &ModuleGraph,
cache: &dyn Cacher,
ts_config: &TsConfig,
reload: bool,
reload_exclusions: &HashSet<ModuleSpecifier>,
) -> bool {
let config_bytes = ts_config.as_bytes();
let emit_js = ts_config.get_check_js();
graph
.specifiers()
.iter()
.filter(|(_, r)| match r {
Ok((_, MediaType::TypeScript))
| Ok((_, MediaType::Tsx))
| Ok((_, MediaType::Jsx)) => true,
Ok((_, MediaType::JavaScript)) => emit_js,
_ => false,
})
.all(|(_, r)| {
if let Ok((s, _)) = r {
if reload && !reload_exclusions.contains(s) {
// we are reloading and the specifier isn't excluded from being
// reloaded
false
} else if let Some(version) = cache.get(CacheType::Version, s) {
if let Some(module) = graph.get(s) {
version == get_version(module.source.as_bytes(), &config_bytes)
} else {
// We have a source module in the graph we can't find, so the emit is
// clearly wrong
false
}
} else {
// A module that requires emitting doesn't have a version, so it doesn't
// have a valid emit
false
}
} else {
// Something in the module graph is missing, but that doesn't mean the
// emit is invalid
true
}
})
}
/// An adapter struct to make a deno_graph::ModuleGraphError display as expected
/// in the Deno CLI.
#[derive(Debug)]
pub(crate) struct GraphError(pub ModuleGraphError);
impl std::error::Error for GraphError {}
impl From<ModuleGraphError> for GraphError {
fn from(err: ModuleGraphError) -> Self {
Self(err)
}
}
impl fmt::Display for GraphError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
ModuleGraphError::ResolutionError(err) => {
if matches!(
err,
ResolutionError::InvalidDowngrade(_, _)
| ResolutionError::InvalidLocalImport(_, _)
) {
write!(f, "{}", err.to_string_with_span())
} else {
self.0.fmt(f)
}
}
_ => self.0.fmt(f),
}
}
}
/// Convert a module graph to a map of "files", which are used by the runtime
/// emit to be passed back to the caller.
pub(crate) fn to_file_map(
graph: &ModuleGraph,
cache: &dyn Cacher,
) -> HashMap<String, String> {
let mut files = HashMap::new();
for (_, result) in graph.specifiers().into_iter() {
if let Ok((specifier, media_type)) = result {
if let Some(emit) = cache.get(CacheType::Emit, &specifier) {
files.insert(format!("{}.js", specifier), emit);
if let Some(map) = cache.get(CacheType::SourceMap, &specifier) {
files.insert(format!("{}.js.map", specifier), map);
}
} else if media_type == MediaType::JavaScript
|| media_type == MediaType::Unknown
{
if let Some(module) = graph.get(&specifier) {
files.insert(specifier.to_string(), module.source.to_string());
}
}
if let Some(declaration) = cache.get(CacheType::Declaration, &specifier) {
files.insert(format!("{}.d.ts", specifier), declaration);
}
}
}
files
}
/// Convert a module graph to a map of module sources, which are used by
/// `deno_core` to load modules into V8.
pub(crate) fn to_module_sources(
graph: &ModuleGraph,
cache: &dyn Cacher,
) -> Modules {
graph
.specifiers()
.into_iter()
.map(|(requested_specifier, r)| match r {
Err(err) => (requested_specifier, Err(err.into())),
Ok((found_specifier, media_type)) => {
// First we check to see if there is an emitted file in the cache.
if let Some(code) = cache.get(CacheType::Emit, &found_specifier) {
(
requested_specifier.clone(),
Ok(ModuleSource {
code,
module_url_found: found_specifier.to_string(),
module_url_specified: requested_specifier.to_string(),
}),
)
// Then if the file is JavaScript (or unknown) and wasn't emitted, we
// will load the original source code in the module.
} else if media_type == MediaType::JavaScript
|| media_type == MediaType::Unknown
{
if let Some(module) = graph.get(&found_specifier) {
(
requested_specifier.clone(),
Ok(ModuleSource {
code: module.source.as_str().to_string(),
module_url_found: module.specifier.to_string(),
module_url_specified: requested_specifier.to_string(),
}),
)
} else {
unreachable!(
"unexpected module missing from graph: {}",
found_specifier
)
}
// Otherwise we will add a not found error.
} else {
(
requested_specifier.clone(),
Err(custom_error(
"NotFound",
format!(
"Emitted code for \"{}\" not found.",
requested_specifier
),
)),
)
}
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::MemoryCacher;
#[test]
fn test_is_emittable() {
assert!(is_emittable(&MediaType::TypeScript, false));
assert!(!is_emittable(&MediaType::Dts, false));
assert!(is_emittable(&MediaType::Tsx, false));
assert!(!is_emittable(&MediaType::JavaScript, false));
assert!(is_emittable(&MediaType::JavaScript, true));
assert!(is_emittable(&MediaType::Jsx, false));
assert!(!is_emittable(&MediaType::Json, false));
}
async fn setup<S: AsRef<str>>(
root: S,
sources: Vec<(S, S)>,
) -> (ModuleGraph, MemoryCacher) {
let roots = vec![ModuleSpecifier::parse(root.as_ref()).unwrap()];
let sources = sources
.into_iter()
.map(|(s, c)| (s.as_ref().to_string(), Arc::new(c.as_ref().to_string())))
.collect();
let mut cache = MemoryCacher::new(sources);
let graph = deno_graph::create_graph(
roots, false, None, &mut cache, None, None, None,
)
.await;
(graph, cache)
}
#[tokio::test]
async fn test_to_module_sources_emitted() {
let (graph, mut cache) = setup(
"https://example.com/a.ts",
vec![("https://example.com/a.ts", r#"console.log("hello deno");"#)],
)
.await;
let (ts_config, _) = get_ts_config(ConfigType::Emit, None, None).unwrap();
emit(
&graph,
&mut cache,
EmitOptions {
ts_config,
reload_exclusions: HashSet::default(),
reload: false,
},
)
.unwrap();
let modules = to_module_sources(&graph, &cache);
assert_eq!(modules.len(), 1);
let root = ModuleSpecifier::parse("https://example.com/a.ts").unwrap();
let maybe_result = modules.get(&root);
assert!(maybe_result.is_some());
let result = maybe_result.unwrap();
assert!(result.is_ok());
let module_source = result.as_ref().unwrap();
assert!(module_source
.code
.starts_with(r#"console.log("hello deno");"#));
}
#[tokio::test]
async fn test_to_module_sources_not_emitted() {
let (graph, mut cache) = setup(
"https://example.com/a.js",
vec![("https://example.com/a.js", r#"console.log("hello deno");"#)],
)
.await;
let (ts_config, _) = get_ts_config(ConfigType::Emit, None, None).unwrap();
emit(
&graph,
&mut cache,
EmitOptions {
ts_config,
reload_exclusions: HashSet::default(),
reload: false,
},
)
.unwrap();
let modules = to_module_sources(&graph, &cache);
assert_eq!(modules.len(), 1);
let root = ModuleSpecifier::parse("https://example.com/a.js").unwrap();
let maybe_result = modules.get(&root);
assert!(maybe_result.is_some());
let result = maybe_result.unwrap();
assert!(result.is_ok());
let module_source = result.as_ref().unwrap();
assert_eq!(module_source.code, r#"console.log("hello deno");"#);
}
}

View file

@ -9,8 +9,12 @@
//! Diagnostics are compile-time type errors, whereas JsErrors are runtime //! Diagnostics are compile-time type errors, whereas JsErrors are runtime
//! exceptions. //! exceptions.
use crate::emit::GraphError;
use deno_ast::Diagnostic; use deno_ast::Diagnostic;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_graph::ModuleGraphError;
use deno_graph::ResolutionError;
use import_map::ImportMapError; use import_map::ImportMapError;
fn get_import_map_error_class(_: &ImportMapError) -> &'static str { fn get_import_map_error_class(_: &ImportMapError) -> &'static str {
@ -21,6 +25,34 @@ fn get_diagnostic_class(_: &Diagnostic) -> &'static str {
"SyntaxError" "SyntaxError"
} }
fn get_graph_error_class(err: &GraphError) -> &'static str {
get_module_graph_error_class(&err.0)
}
pub(crate) fn get_module_graph_error_class(
err: &ModuleGraphError,
) -> &'static str {
match err {
ModuleGraphError::LoadingErr(_, err) => get_error_class_name(err.as_ref()),
ModuleGraphError::InvalidSource(_, _) => "SyntaxError",
ModuleGraphError::ParseErr(_, diagnostic) => {
get_diagnostic_class(diagnostic)
}
ModuleGraphError::ResolutionError(err) => get_resolution_error_class(err),
ModuleGraphError::UnsupportedMediaType(_, _) => "TypeError",
ModuleGraphError::Missing(_) => "NotFound",
}
}
fn get_resolution_error_class(err: &ResolutionError) -> &'static str {
match err {
ResolutionError::ResolverError(err, _, _) => {
get_error_class_name(err.as_ref())
}
_ => "TypeError",
}
}
pub(crate) fn get_error_class_name(e: &AnyError) -> &'static str { pub(crate) fn get_error_class_name(e: &AnyError) -> &'static str {
deno_runtime::errors::get_error_class_name(e) deno_runtime::errors::get_error_class_name(e)
.or_else(|| { .or_else(|| {
@ -28,6 +60,15 @@ pub(crate) fn get_error_class_name(e: &AnyError) -> &'static str {
.map(get_import_map_error_class) .map(get_import_map_error_class)
}) })
.or_else(|| e.downcast_ref::<Diagnostic>().map(get_diagnostic_class)) .or_else(|| e.downcast_ref::<Diagnostic>().map(get_diagnostic_class))
.or_else(|| e.downcast_ref::<GraphError>().map(get_graph_error_class))
.or_else(|| {
e.downcast_ref::<ModuleGraphError>()
.map(get_module_graph_error_class)
})
.or_else(|| {
e.downcast_ref::<ResolutionError>()
.map(get_resolution_error_class)
})
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"Error '{}' contains boxed error of unknown type:{}", "Error '{}' contains boxed error of unknown type:{}",

View file

@ -371,7 +371,9 @@ impl FileFetcher {
})?; })?;
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("content-type".to_string(), content_type); headers.insert("content-type".to_string(), content_type);
self.http_cache.set(specifier, headers, source.as_bytes())?; self
.http_cache
.set(specifier, headers.clone(), source.as_bytes())?;
Ok(File { Ok(File {
local, local,
@ -379,7 +381,7 @@ impl FileFetcher {
media_type, media_type,
source: Arc::new(source), source: Arc::new(source),
specifier: specifier.clone(), specifier: specifier.clone(),
maybe_headers: None, maybe_headers: Some(headers),
}) })
} }
@ -433,7 +435,9 @@ impl FileFetcher {
})?; })?;
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("content-type".to_string(), content_type); headers.insert("content-type".to_string(), content_type);
self.http_cache.set(specifier, headers, source.as_bytes())?; self
.http_cache
.set(specifier, headers.clone(), source.as_bytes())?;
Ok(File { Ok(File {
local, local,
@ -441,7 +445,7 @@ impl FileFetcher {
media_type, media_type,
source: Arc::new(source), source: Arc::new(source),
specifier: specifier.clone(), specifier: specifier.clone(),
maybe_headers: None, maybe_headers: Some(headers),
}) })
} }
/// Asynchronously fetch remote source file specified by the URL following /// Asynchronously fetch remote source file specified by the URL following
@ -573,6 +577,14 @@ impl FileFetcher {
} }
} }
pub fn get_local_path(&self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
if specifier.scheme() == "file" {
specifier.to_file_path().ok()
} else {
self.http_cache.get_cache_filename(specifier)
}
}
/// Get the location of the current HTTP cache associated with the fetcher. /// Get the location of the current HTTP cache associated with the fetcher.
pub fn get_http_cache_location(&self) -> PathBuf { pub fn get_http_cache_location(&self) -> PathBuf {
self.http_cache.location.clone() self.http_cache.location.clone()

View file

@ -1,513 +0,0 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use deno_ast::MediaType;
use deno_core::resolve_url;
use deno_core::serde::Serialize;
use deno_core::ModuleSpecifier;
use std::collections::HashSet;
use std::fmt;
use std::iter::Iterator;
use std::path::PathBuf;
const SIBLING_CONNECTOR: char = '├';
const LAST_SIBLING_CONNECTOR: char = '└';
const CHILD_DEPS_CONNECTOR: char = '┬';
const CHILD_NO_DEPS_CONNECTOR: char = '─';
const VERTICAL_CONNECTOR: char = '│';
const EMPTY_CONNECTOR: char = ' ';
#[derive(Debug, Serialize, Ord, PartialOrd, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfoDep {
pub specifier: String,
#[serde(skip_serializing_if = "is_false")]
pub is_dynamic: bool,
#[serde(rename = "code", skip_serializing_if = "Option::is_none")]
pub maybe_code: Option<ModuleSpecifier>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub maybe_type: Option<ModuleSpecifier>,
}
fn is_false(b: &bool) -> bool {
!b
}
impl ModuleGraphInfoDep {
fn write_info<S: AsRef<str> + fmt::Display + Clone>(
&self,
f: &mut fmt::Formatter,
prefix: S,
last: bool,
modules: &[ModuleGraphInfoMod],
seen: &mut HashSet<ModuleSpecifier>,
) -> fmt::Result {
let maybe_code = self
.maybe_code
.as_ref()
.and_then(|s| modules.iter().find(|m| &m.specifier == s));
let maybe_type = self
.maybe_type
.as_ref()
.and_then(|s| modules.iter().find(|m| &m.specifier == s));
match (maybe_code, maybe_type) {
(Some(code), Some(types)) => {
code.write_info(f, prefix.clone(), false, false, modules, seen)?;
types.write_info(f, prefix, last, true, modules, seen)
}
(Some(code), None) => {
code.write_info(f, prefix, last, false, modules, seen)
}
(None, Some(types)) => {
types.write_info(f, prefix, last, true, modules, seen)
}
_ => Ok(()),
}
}
}
#[derive(Debug, Serialize, Ord, PartialOrd, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfoMod {
pub specifier: ModuleSpecifier,
pub dependencies: Vec<ModuleGraphInfoDep>,
#[serde(rename = "typeDependency", skip_serializing_if = "Option::is_none")]
pub maybe_type_dependency: Option<ModuleGraphInfoDep>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_type: Option<MediaType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub local: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksum: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub emit: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub map: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl Default for ModuleGraphInfoMod {
fn default() -> Self {
ModuleGraphInfoMod {
specifier: resolve_url("https://deno.land/x/mod.ts").unwrap(),
dependencies: Vec::new(),
maybe_type_dependency: None,
size: None,
media_type: None,
local: None,
checksum: None,
emit: None,
map: None,
error: None,
}
}
}
impl ModuleGraphInfoMod {
fn write_info<S: AsRef<str> + fmt::Display>(
&self,
f: &mut fmt::Formatter,
prefix: S,
last: bool,
type_dep: bool,
modules: &[ModuleGraphInfoMod],
seen: &mut HashSet<ModuleSpecifier>,
) -> fmt::Result {
let was_seen = seen.contains(&self.specifier);
let sibling_connector = if last {
LAST_SIBLING_CONNECTOR
} else {
SIBLING_CONNECTOR
};
let child_connector = if (self.dependencies.is_empty()
&& self.maybe_type_dependency.is_none())
|| was_seen
{
CHILD_NO_DEPS_CONNECTOR
} else {
CHILD_DEPS_CONNECTOR
};
let (size, specifier) = if self.error.is_some() {
(
colors::red_bold(" (error)").to_string(),
colors::red(&self.specifier).to_string(),
)
} else if was_seen {
let name = if type_dep {
colors::italic_gray(&self.specifier).to_string()
} else {
colors::gray(&self.specifier).to_string()
};
(colors::gray(" *").to_string(), name)
} else {
let name = if type_dep {
colors::italic(&self.specifier).to_string()
} else {
self.specifier.to_string()
};
(
colors::gray(format!(
" ({})",
human_size(self.size.unwrap_or(0) as f64)
))
.to_string(),
name,
)
};
seen.insert(self.specifier.clone());
writeln!(
f,
"{} {}{}",
colors::gray(format!(
"{}{}─{}",
prefix, sibling_connector, child_connector
)),
specifier,
size
)?;
if !was_seen {
let mut prefix = prefix.to_string();
if last {
prefix.push(EMPTY_CONNECTOR);
} else {
prefix.push(VERTICAL_CONNECTOR);
}
prefix.push(EMPTY_CONNECTOR);
let dep_count = self.dependencies.len();
for (idx, dep) in self.dependencies.iter().enumerate() {
dep.write_info(
f,
&prefix,
idx == dep_count - 1 && self.maybe_type_dependency.is_none(),
modules,
seen,
)?;
}
if let Some(dep) = &self.maybe_type_dependency {
dep.write_info(f, &prefix, true, modules, seen)?;
}
}
Ok(())
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfo {
pub root: ModuleSpecifier,
pub modules: Vec<ModuleGraphInfoMod>,
pub size: usize,
}
impl fmt::Display for ModuleGraphInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let root = self
.modules
.iter()
.find(|m| m.specifier == self.root)
.unwrap();
if let Some(err) = &root.error {
writeln!(f, "{} {}", colors::red("error:"), err)
} else {
if let Some(local) = &root.local {
writeln!(f, "{} {}", colors::bold("local:"), local.to_string_lossy())?;
}
if let Some(media_type) = &root.media_type {
writeln!(f, "{} {}", colors::bold("type:"), media_type)?;
}
if let Some(emit) = &root.emit {
writeln!(f, "{} {}", colors::bold("emit:"), emit.to_string_lossy())?;
}
if let Some(map) = &root.map {
writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?;
}
let dep_count = self.modules.len() - 1;
writeln!(
f,
"{} {} unique {}",
colors::bold("dependencies:"),
dep_count,
colors::gray(format!("(total {})", human_size(self.size as f64)))
)?;
writeln!(
f,
"\n{} {}",
self.root,
colors::gray(format!(
"({})",
human_size(root.size.unwrap_or(0) as f64)
))
)?;
let mut seen = HashSet::new();
let dep_len = root.dependencies.len();
for (idx, dep) in root.dependencies.iter().enumerate() {
dep.write_info(
f,
"",
idx == dep_len - 1 && root.maybe_type_dependency.is_none(),
&self.modules,
&mut seen,
)?;
}
if let Some(dep) = &root.maybe_type_dependency {
dep.write_info(f, "", true, &self.modules, &mut seen)?;
}
Ok(())
}
}
}
/// An entry in the `ModuleInfoMap` the provides the size of the module and
/// a vector of its dependencies, which should also be available as entries
/// in the map.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleInfoMapItem {
pub deps: Vec<ModuleSpecifier>,
pub size: usize,
}
/// A function that converts a float to a string the represents a human
/// readable version of that number.
pub fn human_size(size: f64) -> String {
let negative = if size.is_sign_positive() { "" } else { "-" };
let size = size.abs();
let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
if size < 1_f64 {
return format!("{}{}{}", negative, size, "B");
}
let delimiter = 1024_f64;
let exponent = std::cmp::min(
(size.ln() / delimiter.ln()).floor() as i32,
(units.len() - 1) as i32,
);
let pretty_bytes = format!("{:.2}", size / delimiter.powi(exponent))
.parse::<f64>()
.unwrap()
* 1_f64;
let unit = units[exponent as usize];
format!("{}{}{}", negative, pretty_bytes, unit)
}
#[cfg(test)]
mod test {
use super::*;
use deno_core::resolve_url;
use deno_core::serde_json::json;
#[test]
fn human_size_test() {
assert_eq!(human_size(16_f64), "16B");
assert_eq!(human_size((16 * 1024) as f64), "16KB");
assert_eq!(human_size((16 * 1024 * 1024) as f64), "16MB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(3.0)), "16GB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(4.0)), "16TB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(5.0)), "16PB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(6.0)), "16EB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(7.0)), "16ZB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(8.0)), "16YB");
}
fn get_fixture() -> ModuleGraphInfo {
let specifier_a = resolve_url("https://deno.land/x/a.ts").unwrap();
let specifier_b = resolve_url("https://deno.land/x/b.ts").unwrap();
let specifier_c_js = resolve_url("https://deno.land/x/c.js").unwrap();
let specifier_c_dts = resolve_url("https://deno.land/x/c.d.ts").unwrap();
let specifier_d_js = resolve_url("https://deno.land/x/d.js").unwrap();
let specifier_d_dts = resolve_url("https://deno.land/x/d.d.ts").unwrap();
let modules = vec![
ModuleGraphInfoMod {
specifier: specifier_a.clone(),
dependencies: vec![ModuleGraphInfoDep {
specifier: "./b.ts".to_string(),
is_dynamic: false,
maybe_code: Some(specifier_b.clone()),
maybe_type: None,
}],
size: Some(123),
media_type: Some(MediaType::TypeScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/a.ts")),
checksum: Some("abcdef".to_string()),
emit: Some(PathBuf::from("/cache/emit/https/deno.land/x/a.js")),
..Default::default()
},
ModuleGraphInfoMod {
specifier: specifier_b,
dependencies: vec![ModuleGraphInfoDep {
specifier: "./c.js".to_string(),
is_dynamic: false,
maybe_code: Some(specifier_c_js.clone()),
maybe_type: Some(specifier_c_dts.clone()),
}],
size: Some(456),
media_type: Some(MediaType::TypeScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/b.ts")),
checksum: Some("def123".to_string()),
emit: Some(PathBuf::from("/cache/emit/https/deno.land/x/b.js")),
..Default::default()
},
ModuleGraphInfoMod {
specifier: specifier_c_js,
dependencies: vec![ModuleGraphInfoDep {
specifier: "./d.js".to_string(),
is_dynamic: false,
maybe_code: Some(specifier_d_js.clone()),
maybe_type: None,
}],
size: Some(789),
media_type: Some(MediaType::JavaScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/c.js")),
checksum: Some("9876abcef".to_string()),
..Default::default()
},
ModuleGraphInfoMod {
specifier: specifier_c_dts,
size: Some(999),
media_type: Some(MediaType::Dts),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/c.d.ts")),
checksum: Some("a2b3c4d5".to_string()),
..Default::default()
},
ModuleGraphInfoMod {
specifier: specifier_d_js,
size: Some(987),
maybe_type_dependency: Some(ModuleGraphInfoDep {
specifier: "/x/d.d.ts".to_string(),
is_dynamic: false,
maybe_code: None,
maybe_type: Some(specifier_d_dts.clone()),
}),
media_type: Some(MediaType::JavaScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/d.js")),
checksum: Some("5k6j7h8g".to_string()),
..Default::default()
},
ModuleGraphInfoMod {
specifier: specifier_d_dts,
size: Some(67),
media_type: Some(MediaType::Dts),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/d.d.ts")),
checksum: Some("0h0h0h0h0h".to_string()),
..Default::default()
},
];
ModuleGraphInfo {
root: specifier_a,
modules,
size: 99999,
}
}
#[test]
fn text_module_graph_info_display() {
let fixture = get_fixture();
let text = fixture.to_string();
let actual = test_util::strip_ansi_codes(&text);
let expected = r#"local: /cache/deps/https/deno.land/x/a.ts
type: TypeScript
emit: /cache/emit/https/deno.land/x/a.js
dependencies: 5 unique (total 97.66KB)
https://deno.land/x/a.ts (123B)
https://deno.land/x/b.ts (456B)
https://deno.land/x/c.js (789B)
https://deno.land/x/d.js (987B)
https://deno.land/x/d.d.ts (67B)
https://deno.land/x/c.d.ts (999B)
"#;
assert_eq!(actual, expected);
}
#[test]
fn test_module_graph_info_json() {
let fixture = get_fixture();
let actual = json!(fixture);
assert_eq!(
actual,
json!({
"root": "https://deno.land/x/a.ts",
"modules": [
{
"specifier": "https://deno.land/x/a.ts",
"dependencies": [
{
"specifier": "./b.ts",
"code": "https://deno.land/x/b.ts"
}
],
"size": 123,
"mediaType": "TypeScript",
"local": "/cache/deps/https/deno.land/x/a.ts",
"checksum": "abcdef",
"emit": "/cache/emit/https/deno.land/x/a.js"
},
{
"specifier": "https://deno.land/x/b.ts",
"dependencies": [
{
"specifier": "./c.js",
"code": "https://deno.land/x/c.js",
"type": "https://deno.land/x/c.d.ts"
}
],
"size": 456,
"mediaType": "TypeScript",
"local": "/cache/deps/https/deno.land/x/b.ts",
"checksum": "def123",
"emit": "/cache/emit/https/deno.land/x/b.js"
},
{
"specifier": "https://deno.land/x/c.js",
"dependencies": [
{
"specifier": "./d.js",
"code": "https://deno.land/x/d.js"
}
],
"size": 789,
"mediaType": "JavaScript",
"local": "/cache/deps/https/deno.land/x/c.js",
"checksum": "9876abcef"
},
{
"specifier": "https://deno.land/x/c.d.ts",
"dependencies": [],
"size": 999,
"mediaType": "Dts",
"local": "/cache/deps/https/deno.land/x/c.d.ts",
"checksum": "a2b3c4d5"
},
{
"specifier": "https://deno.land/x/d.js",
"dependencies": [],
"typeDependency": {
"specifier": "/x/d.d.ts",
"type": "https://deno.land/x/d.d.ts"
},
"size": 987,
"mediaType": "JavaScript",
"local": "/cache/deps/https/deno.land/x/d.js",
"checksum": "5k6j7h8g"
},
{
"specifier": "https://deno.land/x/d.d.ts",
"dependencies": [],
"size": 67,
"mediaType": "Dts",
"local": "/cache/deps/https/deno.land/x/d.d.ts",
"checksum": "0h0h0h0h0h"
}
],
"size": 99999
})
);
}
}

View file

@ -1,11 +1,16 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::parking_lot::Mutex;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::ModuleSpecifier;
use log::debug; use log::debug;
use std::cell::RefCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io::Result; use std::io::Result;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Lockfile { pub struct Lockfile {
@ -81,6 +86,43 @@ impl Lockfile {
} }
} }
#[derive(Debug)]
pub(crate) struct Locker(Option<Arc<Mutex<Lockfile>>>);
impl deno_graph::source::Locker for Locker {
fn check_or_insert(
&mut self,
specifier: &ModuleSpecifier,
source: &str,
) -> bool {
if let Some(lock_file) = &self.0 {
let mut lock_file = lock_file.lock();
lock_file.check_or_insert(specifier.as_str(), source)
} else {
true
}
}
fn get_checksum(&self, content: &str) -> String {
crate::checksum::gen(&[content.as_bytes()])
}
fn get_filename(&self) -> Option<String> {
let lock_file = self.0.as_ref()?.lock();
lock_file.filename.to_str().map(|s| s.to_string())
}
}
pub(crate) fn as_maybe_locker(
lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Option<Rc<RefCell<Box<dyn deno_graph::source::Locker>>>> {
lockfile.as_ref().map(|lf| {
Rc::new(RefCell::new(
Box::new(Locker(Some(lf.clone()))) as Box<dyn deno_graph::source::Locker>
))
})
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -6,12 +6,12 @@ use super::tsc;
use crate::ast; use crate::ast;
use crate::ast::Location; use crate::ast::Location;
use crate::lsp::documents::DocumentData; use crate::lsp::documents::DocumentData;
use crate::module_graph::parse_deno_types;
use crate::module_graph::parse_ts_reference;
use crate::module_graph::TypeScriptReference;
use crate::tools::lint::create_linter; use crate::tools::lint::create_linter;
use deno_ast::swc::ast as swc_ast; use deno_ast::swc::ast as swc_ast;
use deno_ast::swc::common::comments::Comment;
use deno_ast::swc::common::BytePos;
use deno_ast::swc::common::Span;
use deno_ast::swc::common::DUMMY_SP; use deno_ast::swc::common::DUMMY_SP;
use deno_ast::swc::visit::Node; use deno_ast::swc::visit::Node;
use deno_ast::swc::visit::Visit; use deno_ast::swc::visit::Visit;
@ -68,10 +68,71 @@ lazy_static::lazy_static! {
.collect(); .collect();
static ref IMPORT_SPECIFIER_RE: Regex = Regex::new(r#"\sfrom\s+["']([^"']*)["']"#).unwrap(); static ref IMPORT_SPECIFIER_RE: Regex = Regex::new(r#"\sfrom\s+["']([^"']*)["']"#).unwrap();
static ref DENO_TYPES_RE: Regex =
Regex::new(r#"(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"#)
.unwrap();
static ref TRIPLE_SLASH_REFERENCE_RE: Regex =
Regex::new(r"(?i)^/\s*<reference\s.*?/>").unwrap();
static ref PATH_REFERENCE_RE: Regex =
Regex::new(r#"(?i)\spath\s*=\s*["']([^"']*)["']"#).unwrap();
static ref TYPES_REFERENCE_RE: Regex =
Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap();
} }
const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"]; const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"];
// TODO(@kitsonk) remove after deno_graph migration
#[derive(Debug, Clone, Eq, PartialEq)]
enum TypeScriptReference {
Path(String),
Types(String),
}
fn match_to_span(comment: &Comment, m: &regex::Match) -> Span {
Span {
lo: comment.span.lo + BytePos((m.start() + 1) as u32),
hi: comment.span.lo + BytePos((m.end() + 1) as u32),
ctxt: comment.span.ctxt,
}
}
// TODO(@kitsonk) remove after deno_graph migration
fn parse_deno_types(comment: &Comment) -> Option<(String, Span)> {
let captures = DENO_TYPES_RE.captures(&comment.text)?;
if let Some(m) = captures.get(1) {
Some((m.as_str().to_string(), match_to_span(comment, &m)))
} else if let Some(m) = captures.get(2) {
Some((m.as_str().to_string(), match_to_span(comment, &m)))
} else {
unreachable!();
}
}
// TODO(@kitsonk) remove after deno_graph migration
fn parse_ts_reference(
comment: &Comment,
) -> Option<(TypeScriptReference, Span)> {
if !TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) {
None
} else if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) {
let m = captures.get(1).unwrap();
Some((
TypeScriptReference::Path(m.as_str().to_string()),
match_to_span(comment, &m),
))
} else {
TYPES_REFERENCE_RE.captures(&comment.text).map(|captures| {
let m = captures.get(1).unwrap();
(
TypeScriptReference::Types(m.as_str().to_string()),
match_to_span(comment, &m),
)
})
}
}
/// Category of self-generated diagnostic messages (those not coming from) /// Category of self-generated diagnostic messages (those not coming from)
/// TypeScript. /// TypeScript.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]

86
cli/lsp/cache.rs Normal file
View file

@ -0,0 +1,86 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::cache::CacherLoader;
use crate::cache::FetchCacher;
use crate::flags::Flags;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
use crate::tokio_util::create_basic_runtime;
use deno_core::error::anyhow;
use deno_core::error::AnyError;
use deno_core::ModuleSpecifier;
use deno_runtime::permissions::Permissions;
use import_map::ImportMap;
use std::path::PathBuf;
use std::thread;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
type Request = (Vec<ModuleSpecifier>, oneshot::Sender<Result<(), AnyError>>);
/// A "server" that handles requests from the language server to cache modules
/// in its own thread.
#[derive(Debug)]
pub(crate) struct CacheServer(mpsc::UnboundedSender<Request>);
impl CacheServer {
pub async fn new(
maybe_cache_path: Option<PathBuf>,
maybe_import_map: Option<ImportMap>,
) -> Self {
let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
let _join_handle = thread::spawn(move || {
let runtime = create_basic_runtime();
runtime.block_on(async {
let ps = ProcState::build(Flags {
cache_path: maybe_cache_path,
..Default::default()
})
.await
.unwrap();
let maybe_resolver =
maybe_import_map.as_ref().map(ImportMapResolver::new);
let mut cache = FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
Permissions::allow_all(),
Permissions::allow_all(),
);
while let Some((roots, tx)) = rx.recv().await {
let graph = deno_graph::create_graph(
roots,
false,
None,
cache.as_mut_loader(),
maybe_resolver.as_ref().map(|r| r.as_resolver()),
None,
None,
)
.await;
if tx.send(graph.valid().map_err(|err| err.into())).is_err() {
log::warn!("cannot send to client");
}
}
})
});
Self(tx)
}
/// Attempt to cache the supplied module specifiers and their dependencies in
/// the current DENO_DIR, returning any errors, so they can be returned to the
/// client.
pub async fn cache(
&self,
roots: Vec<ModuleSpecifier>,
) -> Result<(), AnyError> {
let (tx, rx) = oneshot::channel::<Result<(), AnyError>>();
if self.0.send((roots, tx)).is_err() {
return Err(anyhow!("failed to send request to cache thread"));
}
rx.await?
}
}

View file

@ -29,6 +29,7 @@ use super::analysis::ts_changes_to_edit;
use super::analysis::CodeActionCollection; use super::analysis::CodeActionCollection;
use super::analysis::CodeActionData; use super::analysis::CodeActionData;
use super::analysis::ResolvedDependency; use super::analysis::ResolvedDependency;
use super::cache::CacheServer;
use super::capabilities; use super::capabilities;
use super::code_lens; use super::code_lens;
use super::completions; use super::completions;
@ -44,7 +45,6 @@ use super::parent_process_checker;
use super::performance::Performance; use super::performance::Performance;
use super::refactor; use super::refactor;
use super::registries; use super::registries;
use super::sources;
use super::sources::Sources; use super::sources::Sources;
use super::text; use super::text;
use super::text::LineIndex; use super::text::LineIndex;
@ -99,6 +99,8 @@ pub(crate) struct Inner {
/// An optional path to the DENO_DIR which has been specified in the client /// An optional path to the DENO_DIR which has been specified in the client
/// options. /// options.
maybe_cache_path: Option<PathBuf>, maybe_cache_path: Option<PathBuf>,
/// A lazily created "server" for handling cache requests.
maybe_cache_server: Option<CacheServer>,
/// An optional configuration file which has been specified in the client /// An optional configuration file which has been specified in the client
/// options. /// options.
maybe_config_file: Option<ConfigFile>, maybe_config_file: Option<ConfigFile>,
@ -149,6 +151,7 @@ impl Inner {
diagnostics_server, diagnostics_server,
documents: Default::default(), documents: Default::default(),
maybe_cache_path: None, maybe_cache_path: None,
maybe_cache_server: None,
maybe_config_file: None, maybe_config_file: None,
maybe_config_uri: None, maybe_config_uri: None,
maybe_import_map: None, maybe_import_map: None,
@ -424,6 +427,7 @@ impl Inner {
pub fn update_cache(&mut self) -> Result<(), AnyError> { pub fn update_cache(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_cache", None::<()>); let mark = self.performance.mark("update_cache", None::<()>);
self.performance.measure(mark); self.performance.measure(mark);
self.maybe_cache_server = None;
let (maybe_cache, maybe_root_uri) = { let (maybe_cache, maybe_root_uri) = {
let config = &self.config; let config = &self.config;
( (
@ -479,6 +483,7 @@ impl Inner {
pub async fn update_import_map(&mut self) -> Result<(), AnyError> { pub async fn update_import_map(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_import_map", None::<()>); let mark = self.performance.mark("update_import_map", None::<()>);
self.maybe_cache_server = None;
let (maybe_import_map, maybe_root_uri) = { let (maybe_import_map, maybe_root_uri) = {
let config = &self.config; let config = &self.config;
( (
@ -2630,34 +2635,30 @@ impl Inner {
} }
let mark = self.performance.mark("cache", Some(&params)); let mark = self.performance.mark("cache", Some(&params));
if !params.uris.is_empty() { let roots = if !params.uris.is_empty() {
for identifier in &params.uris { params
let specifier = self.url_map.normalize_url(&identifier.uri); .uris
sources::cache( .iter()
&specifier, .map(|t| self.url_map.normalize_url(&t.uri))
&self.maybe_import_map, .collect()
&self.maybe_config_file,
&self.maybe_cache_path,
)
.await
.map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?;
}
} else { } else {
sources::cache( vec![referrer.clone()]
&referrer, };
&self.maybe_import_map,
&self.maybe_config_file, if self.maybe_cache_server.is_none() {
&self.maybe_cache_path, self.maybe_cache_server = Some(
) CacheServer::new(
.await self.maybe_cache_path.clone(),
.map_err(|err| { self.maybe_import_map.clone(),
error!("{}", err); )
LspError::internal_error() .await,
})?; );
} }
let cache_server = self.maybe_cache_server.as_ref().unwrap();
if let Err(err) = cache_server.cache(roots).await {
self.client.show_message(MessageType::Warning, err).await;
}
// now that we have dependencies loaded, we need to re-analyze them and // now that we have dependencies loaded, we need to re-analyze them and
// invalidate some diagnostics // invalidate some diagnostics
if self.documents.contains_key(&referrer) { if self.documents.contains_key(&referrer) {

View file

@ -5,6 +5,7 @@ use lspower::LspService;
use lspower::Server; use lspower::Server;
mod analysis; mod analysis;
mod cache;
mod capabilities; mod capabilities;
mod code_lens; mod code_lens;
mod completions; mod completions;

View file

@ -6,18 +6,12 @@ use super::text::LineIndex;
use super::tsc; use super::tsc;
use super::urls::INVALID_SPECIFIER; use super::urls::INVALID_SPECIFIER;
use crate::config_file::ConfigFile;
use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::map_content_type; use crate::file_fetcher::map_content_type;
use crate::file_fetcher::SUPPORTED_SCHEMES; use crate::file_fetcher::SUPPORTED_SCHEMES;
use crate::flags::Flags;
use crate::http_cache; use crate::http_cache;
use crate::http_cache::HttpCache; use crate::http_cache::HttpCache;
use crate::module_graph::GraphBuilder;
use crate::proc_state::ProcState;
use crate::specifier_handler::FetchHandler;
use crate::text_encoding; use crate::text_encoding;
use import_map::ImportMap;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::error::anyhow; use deno_core::error::anyhow;
@ -25,7 +19,7 @@ use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_runtime::permissions::Permissions; use import_map::ImportMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
@ -34,27 +28,6 @@ use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use tsc::NavigationTree; use tsc::NavigationTree;
pub async fn cache(
specifier: &ModuleSpecifier,
maybe_import_map: &Option<ImportMap>,
maybe_config_file: &Option<ConfigFile>,
maybe_cache_path: &Option<PathBuf>,
) -> Result<(), AnyError> {
let ps = ProcState::build(Flags {
cache_path: maybe_cache_path.clone(),
..Default::default()
})
.await?;
let handler = Arc::new(Mutex::new(FetchHandler::new(
&ps,
Permissions::allow_all(),
Permissions::allow_all(),
)?));
let mut builder = GraphBuilder::new(handler, maybe_import_map.clone(), None);
builder.analyze_config_file(maybe_config_file).await?;
builder.add(specifier, false).await
}
fn get_remote_headers( fn get_remote_headers(
cache_filename: &Path, cache_filename: &Path,
) -> Option<HashMap<String, String>> { ) -> Option<HashMap<String, String>> {

View file

@ -44,12 +44,15 @@ use log::warn;
use lspower::lsp; use lspower::lsp;
use regex::Captures; use regex::Captures;
use regex::Regex; use regex::Regex;
use std::borrow::Cow;
use std::cmp;
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use std::{borrow::Cow, cmp}; use text_size::TextRange;
use std::{collections::HashMap, path::Path}; use text_size::TextSize;
use text_size::{TextRange, TextSize};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -3375,7 +3378,7 @@ mod tests {
#[test] #[test]
fn test_modify_sources() { fn test_modify_sources() {
let (mut runtime, state_snapshot, location) = setup( let (mut runtime, state_snapshot, location) = setup(
true, false,
json!({ json!({
"target": "esnext", "target": "esnext",
"module": "esnext", "module": "esnext",

View file

@ -2,6 +2,7 @@
mod ast; mod ast;
mod auth_tokens; mod auth_tokens;
mod cache;
mod checksum; mod checksum;
mod compat; mod compat;
mod config_file; mod config_file;
@ -9,6 +10,7 @@ mod deno_dir;
mod diagnostics; mod diagnostics;
mod diff; mod diff;
mod disk_cache; mod disk_cache;
mod emit;
mod errors; mod errors;
mod file_fetcher; mod file_fetcher;
mod file_watcher; mod file_watcher;
@ -18,16 +20,14 @@ mod fmt_errors;
mod fs_util; mod fs_util;
mod http_cache; mod http_cache;
mod http_util; mod http_util;
mod info;
mod lockfile; mod lockfile;
mod logger; mod logger;
mod lsp; mod lsp;
mod module_graph;
mod module_loader; mod module_loader;
mod ops; mod ops;
mod proc_state; mod proc_state;
mod resolver;
mod source_maps; mod source_maps;
mod specifier_handler;
mod standalone; mod standalone;
mod text_encoding; mod text_encoding;
mod tokio_util; mod tokio_util;
@ -59,8 +59,8 @@ use crate::flags::UpgradeFlags;
use crate::fmt_errors::PrettyJsError; use crate::fmt_errors::PrettyJsError;
use crate::module_loader::CliModuleLoader; use crate::module_loader::CliModuleLoader;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
use crate::source_maps::apply_source_map; use crate::source_maps::apply_source_map;
use crate::specifier_handler::FetchHandler;
use crate::tools::installer::infer_name_from_url; use crate::tools::installer::infer_name_from_url;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::error::generic_error; use deno_core::error::generic_error;
@ -68,7 +68,6 @@ use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt; use deno_core::futures::future::FutureExt;
use deno_core::futures::Future; use deno_core::futures::Future;
use deno_core::located_script_name; use deno_core::located_script_name;
use deno_core::parking_lot::Mutex;
use deno_core::resolve_url_or_path; use deno_core::resolve_url_or_path;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
@ -299,7 +298,7 @@ where
fn print_cache_info( fn print_cache_info(
state: &ProcState, state: &ProcState,
json: bool, json: bool,
location: Option<deno_core::url::Url>, location: Option<&deno_core::url::Url>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let deno_dir = &state.dir.root; let deno_dir = &state.dir.root;
let modules_cache = &state.file_fetcher.get_http_cache_location(); let modules_cache = &state.file_fetcher.get_http_cache_location();
@ -402,19 +401,9 @@ async fn compile_command(
"An executable name was not provided. One could not be inferred from the URL. Aborting.", "An executable name was not provided. One could not be inferred from the URL. Aborting.",
))?; ))?;
let module_graph = create_module_graph_and_maybe_check( let graph =
module_specifier.clone(), create_graph_and_maybe_check(module_specifier.clone(), &ps, debug).await?;
ps.clone(), let (bundle_str, _) = bundle_module_graph(graph.as_ref(), &ps, &flags)?;
debug,
)
.await?;
info!(
"{} {}",
colors::green("Bundle"),
module_specifier.to_string()
);
let bundle_str = bundle_module_graph(module_graph, ps.clone(), flags, debug)?;
info!( info!(
"{} {}", "{} {}",
@ -449,36 +438,38 @@ async fn info_command(
flags: Flags, flags: Flags,
info_flags: InfoFlags, info_flags: InfoFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let location = flags.location.clone();
let ps = ProcState::build(flags).await?; let ps = ProcState::build(flags).await?;
if let Some(specifier) = info_flags.file { if let Some(specifier) = info_flags.file {
let specifier = resolve_url_or_path(&specifier)?; let specifier = resolve_url_or_path(&specifier)?;
let handler = Arc::new(Mutex::new(specifier_handler::FetchHandler::new( let mut cache = cache::FetchCacher::new(
&ps, ps.dir.gen_cache.clone(),
// info accesses dynamically imported modules just for their information ps.file_fetcher.clone(),
// so we allow access to all of them.
Permissions::allow_all(), Permissions::allow_all(),
Permissions::allow_all(), Permissions::allow_all(),
)?));
let mut builder = module_graph::GraphBuilder::new(
handler,
ps.maybe_import_map.clone(),
ps.lockfile.clone(),
); );
builder.add(&specifier, false).await?; let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
builder.analyze_config_file(&ps.maybe_config_file).await?; let maybe_resolver =
let graph = builder.get_graph(); ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
let info = graph.info()?; let graph = deno_graph::create_graph(
vec![specifier],
false,
None,
&mut cache,
maybe_resolver.as_ref().map(|r| r.as_resolver()),
maybe_locker,
None,
)
.await;
if info_flags.json { if info_flags.json {
write_json_to_stdout(&json!(info)) write_json_to_stdout(&json!(graph))
} else { } else {
write_to_stdout_ignore_sigpipe(info.to_string().as_bytes()) write_to_stdout_ignore_sigpipe(graph.to_string().as_bytes())
.map_err(|err| err.into()) .map_err(|err| err.into())
} }
} else { } else {
// If it was just "deno info" print location of caches and exit // If it was just "deno info" print location of caches and exit
print_cache_info(&ps, info_flags.json, location) print_cache_info(&ps, info_flags.json, ps.flags.location.as_ref())
} }
} }
@ -541,23 +532,25 @@ async fn cache_command(
cache_flags: CacheFlags, cache_flags: CacheFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let lib = if flags.unstable { let lib = if flags.unstable {
module_graph::TypeLib::UnstableDenoWindow emit::TypeLib::UnstableDenoWindow
} else { } else {
module_graph::TypeLib::DenoWindow emit::TypeLib::DenoWindow
}; };
let ps = ProcState::build(flags).await?; let ps = ProcState::build(flags).await?;
for file in cache_flags.files { for file in cache_flags.files {
let specifier = resolve_url_or_path(&file)?; let specifier = resolve_url_or_path(&file)?;
ps.prepare_module_load( ps.prepare_module_load(
specifier, vec![specifier],
false,
lib.clone(), lib.clone(),
Permissions::allow_all(), Permissions::allow_all(),
Permissions::allow_all(), Permissions::allow_all(),
false,
ps.maybe_import_map.clone(),
) )
.await?; .await?;
if let Some(graph_error) = ps.maybe_graph_error.lock().take() {
return Err(graph_error.into());
}
} }
Ok(()) Ok(())
@ -567,8 +560,10 @@ async fn eval_command(
flags: Flags, flags: Flags,
eval_flags: EvalFlags, eval_flags: EvalFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
// Force TypeScript compile. // deno_graph works off of extensions for local files to determine the media
let main_module = resolve_url_or_path("./$deno$eval.ts").unwrap(); // type, and so our "fake" specifier needs to have the proper extension.
let main_module =
resolve_url_or_path(&format!("./$deno$eval.{}", eval_flags.ext)).unwrap();
let permissions = Permissions::from_options(&flags.clone().into()); let permissions = Permissions::from_options(&flags.clone().into());
let ps = ProcState::build(flags.clone()).await?; let ps = ProcState::build(flags.clone()).await?;
let mut worker = let mut worker =
@ -584,15 +579,7 @@ async fn eval_command(
let file = File { let file = File {
local: main_module.clone().to_file_path().unwrap(), local: main_module.clone().to_file_path().unwrap(),
maybe_types: None, maybe_types: None,
media_type: if eval_flags.ext.as_str() == "ts" { media_type: MediaType::Unknown,
MediaType::TypeScript
} else if eval_flags.ext.as_str() == "tsx" {
MediaType::Tsx
} else if eval_flags.ext.as_str() == "js" {
MediaType::JavaScript
} else {
MediaType::Jsx
},
source: Arc::new(String::from_utf8(source_code)?), source: Arc::new(String::from_utf8(source_code)?),
specifier: main_module.clone(), specifier: main_module.clone(),
maybe_headers: None, maybe_headers: None,
@ -603,9 +590,7 @@ async fn eval_command(
ps.file_fetcher.insert_cached(file); ps.file_fetcher.insert_cached(file);
debug!("main_module {}", &main_module); debug!("main_module {}", &main_module);
if flags.compat { if flags.compat {
worker worker.execute_side_module(&compat::GLOBAL_URL).await?;
.execute_side_module(&compat::get_node_globals_url())
.await?;
} }
worker.execute_main_module(&main_module).await?; worker.execute_main_module(&main_module).await?;
worker.execute_script( worker.execute_script(
@ -620,75 +605,133 @@ async fn eval_command(
Ok(()) Ok(())
} }
async fn create_module_graph_and_maybe_check( async fn create_graph_and_maybe_check(
module_specifier: ModuleSpecifier, root: ModuleSpecifier,
ps: ProcState, ps: &ProcState,
debug: bool, debug: bool,
) -> Result<module_graph::Graph, AnyError> { ) -> Result<Arc<deno_graph::ModuleGraph>, AnyError> {
let handler = Arc::new(Mutex::new(FetchHandler::new( let mut cache = cache::FetchCacher::new(
&ps, ps.dir.gen_cache.clone(),
// when bundling, dynamic imports are only access for their type safety, ps.file_fetcher.clone(),
// therefore we will allow the graph to access any module.
Permissions::allow_all(), Permissions::allow_all(),
Permissions::allow_all(), Permissions::allow_all(),
)?));
let mut builder = module_graph::GraphBuilder::new(
handler,
ps.maybe_import_map.clone(),
ps.lockfile.clone(),
); );
builder.add(&module_specifier, false).await?; let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
builder.analyze_config_file(&ps.maybe_config_file).await?; let maybe_imports = ps
let module_graph = builder.get_graph(); .maybe_config_file
.as_ref()
.map(|cf| cf.to_maybe_imports())
.flatten();
let maybe_resolver = ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
let graph = Arc::new(
deno_graph::create_graph(
vec![root],
false,
maybe_imports,
&mut cache,
maybe_resolver.as_ref().map(|r| r.as_resolver()),
maybe_locker,
None,
)
.await,
);
// Ensure that all non-dynamic, non-type only imports are properly loaded and
// if not, error with the first issue encountered.
graph.valid().map_err(emit::GraphError::from)?;
// If there was a locker, validate the integrity of all the modules in the
// locker.
emit::lock(graph.as_ref());
if !ps.flags.no_check { if !ps.flags.no_check {
// TODO(@kitsonk) support bundling for workers graph.valid_types_only().map_err(emit::GraphError::from)?;
let lib = if ps.flags.unstable { let lib = if ps.flags.unstable {
module_graph::TypeLib::UnstableDenoWindow emit::TypeLib::UnstableDenoWindow
} else { } else {
module_graph::TypeLib::DenoWindow emit::TypeLib::DenoWindow
}; };
let result_info = let (ts_config, maybe_ignored_options) = emit::get_ts_config(
module_graph.clone().check(module_graph::CheckOptions { emit::ConfigType::Check {
debug, tsc_emit: false,
emit: false,
lib, lib,
maybe_config_file: ps.maybe_config_file.clone(), },
reload: ps.flags.reload, ps.maybe_config_file.as_ref(),
..Default::default() None,
})?; )?;
log::info!("{} {}", colors::green("Check"), graph.roots[0]);
debug!("{}", result_info.stats); if let Some(ignored_options) = maybe_ignored_options {
if let Some(ignored_options) = result_info.maybe_ignored_options {
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
if !result_info.diagnostics.is_empty() { let maybe_config_specifier = ps
return Err(generic_error(result_info.diagnostics.to_string())); .maybe_config_file
.as_ref()
.map(|cf| ModuleSpecifier::from_file_path(&cf.path).unwrap());
let check_result = emit::check_and_maybe_emit(
graph.clone(),
&mut cache,
emit::CheckOptions {
debug,
emit_with_diagnostics: false,
maybe_config_specifier,
ts_config,
},
)?;
debug!("{}", check_result.stats);
if !check_result.diagnostics.is_empty() {
return Err(check_result.diagnostics.into());
} }
} }
Ok(module_graph) Ok(graph)
} }
fn bundle_module_graph( fn bundle_module_graph(
module_graph: module_graph::Graph, graph: &deno_graph::ModuleGraph,
ps: ProcState, ps: &ProcState,
flags: Flags, flags: &Flags,
debug: bool, ) -> Result<(String, Option<String>), AnyError> {
) -> Result<String, AnyError> { info!("{} {}", colors::green("Bundle"), graph.roots[0]);
let (bundle, stats, maybe_ignored_options) =
module_graph.bundle(module_graph::BundleOptions { let (ts_config, maybe_ignored_options) = emit::get_ts_config(
debug, emit::ConfigType::Bundle,
maybe_config_file: ps.maybe_config_file.clone(), ps.maybe_config_file.as_ref(),
})?; None,
match maybe_ignored_options { )?;
Some(ignored_options) if flags.no_check => { if flags.no_check {
if let Some(ignored_options) = maybe_ignored_options {
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
_ => {}
} }
debug!("{}", stats);
Ok(bundle) emit::bundle(
graph,
emit::BundleOptions {
bundle_type: emit::BundleType::Module,
ts_config,
},
)
}
/// A function that converts a float to a string the represents a human
/// readable version of that number.
fn human_size(size: f64) -> String {
let negative = if size.is_sign_positive() { "" } else { "-" };
let size = size.abs();
let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
if size < 1_f64 {
return format!("{}{}{}", negative, size, "B");
}
let delimiter = 1024_f64;
let exponent = std::cmp::min(
(size.ln() / delimiter.ln()).floor() as i32,
(units.len() - 1) as i32,
);
let pretty_bytes = format!("{:.2}", size / delimiter.powi(exponent))
.parse::<f64>()
.unwrap()
* 1_f64;
let unit = units[exponent as usize];
format!("{}{}{}", negative, pretty_bytes, unit)
} }
async fn bundle_command( async fn bundle_command(
@ -707,17 +750,18 @@ async fn bundle_command(
debug!(">>>>> bundle START"); debug!(">>>>> bundle START");
let ps = ProcState::build(flags.clone()).await?; let ps = ProcState::build(flags.clone()).await?;
let module_graph = create_module_graph_and_maybe_check( let graph =
module_specifier, create_graph_and_maybe_check(module_specifier, &ps, debug).await?;
ps.clone(),
debug,
)
.await?;
let mut paths_to_watch: Vec<PathBuf> = module_graph let mut paths_to_watch: Vec<PathBuf> = graph
.get_modules() .specifiers()
.iter() .iter()
.filter_map(|specifier| specifier.to_file_path().ok()) .filter_map(|(_, r)| {
r.as_ref()
.ok()
.map(|(s, _)| s.to_file_path().ok())
.flatten()
})
.collect(); .collect();
if let Some(import_map) = ps.flags.import_map_path.as_ref() { if let Some(import_map) = ps.flags.import_map_path.as_ref() {
@ -725,12 +769,12 @@ async fn bundle_command(
.push(fs_util::resolve_from_cwd(std::path::Path::new(import_map))?); .push(fs_util::resolve_from_cwd(std::path::Path::new(import_map))?);
} }
Ok((paths_to_watch, module_graph, ps)) Ok((paths_to_watch, graph, ps))
} }
.map(move |result| match result { .map(move |result| match result {
Ok((paths_to_watch, module_graph, ps)) => ResolutionResult::Restart { Ok((paths_to_watch, graph, ps)) => ResolutionResult::Restart {
paths_to_watch, paths_to_watch,
result: Ok((ps, module_graph)), result: Ok((ps, graph)),
}, },
Err(e) => ResolutionResult::Restart { Err(e) => ResolutionResult::Restart {
paths_to_watch: vec![PathBuf::from(source_file2)], paths_to_watch: vec![PathBuf::from(source_file2)],
@ -739,28 +783,43 @@ async fn bundle_command(
}) })
}; };
let operation = |(ps, module_graph): (ProcState, module_graph::Graph)| { let operation = |(ps, graph): (ProcState, Arc<deno_graph::ModuleGraph>)| {
let flags = flags.clone(); let flags = flags.clone();
let out_file = bundle_flags.out_file.clone(); let out_file = bundle_flags.out_file.clone();
async move { async move {
info!("{} {}", colors::green("Bundle"), module_graph.info()?.root); let (bundle_emit, maybe_bundle_map) =
bundle_module_graph(graph.as_ref(), &ps, &flags)?;
let output = bundle_module_graph(module_graph, ps, flags, debug)?;
debug!(">>>>> bundle END"); debug!(">>>>> bundle END");
if let Some(out_file) = out_file.as_ref() { if let Some(out_file) = out_file.as_ref() {
let output_bytes = output.as_bytes(); let output_bytes = bundle_emit.as_bytes();
let output_len = output_bytes.len(); let output_len = output_bytes.len();
fs_util::write_file(out_file, output_bytes, 0o644)?; fs_util::write_file(out_file, output_bytes, 0o644)?;
info!( info!(
"{} {:?} ({})", "{} {:?} ({})",
colors::green("Emit"), colors::green("Emit"),
out_file, out_file,
colors::gray(&info::human_size(output_len as f64)) colors::gray(human_size(output_len as f64))
); );
if let Some(bundle_map) = maybe_bundle_map {
let map_bytes = bundle_map.as_bytes();
let map_len = map_bytes.len();
let ext = if let Some(curr_ext) = out_file.extension() {
format!("{}.map", curr_ext.to_string_lossy())
} else {
"map".to_string()
};
let map_out_file = out_file.with_extension(ext);
fs_util::write_file(&map_out_file, map_bytes, 0o644)?;
info!(
"{} {:?} ({})",
colors::green("Emit"),
map_out_file,
colors::gray(human_size(map_len as f64))
);
}
} else { } else {
println!("{}", output); println!("{}", bundle_emit);
} }
Ok(()) Ok(())
@ -825,9 +884,7 @@ async fn run_repl(flags: Flags, repl_flags: ReplFlags) -> Result<(), AnyError> {
let mut worker = let mut worker =
create_main_worker(&ps, main_module.clone(), permissions, None); create_main_worker(&ps, main_module.clone(), permissions, None);
if flags.compat { if flags.compat {
worker worker.execute_side_module(&compat::GLOBAL_URL).await?;
.execute_side_module(&compat::get_node_globals_url())
.await?;
} }
worker.run_event_loop(false).await?; worker.run_event_loop(false).await?;
@ -858,9 +915,7 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> {
debug!("main_module {}", main_module); debug!("main_module {}", main_module);
if flags.compat { if flags.compat {
worker worker.execute_side_module(&compat::GLOBAL_URL).await?;
.execute_side_module(&compat::get_node_globals_url())
.await?;
} }
worker.execute_main_module(&main_module).await?; worker.execute_main_module(&main_module).await?;
worker.execute_script( worker.execute_script(
@ -883,25 +938,42 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
async move { async move {
let main_module = resolve_url_or_path(&script1)?; let main_module = resolve_url_or_path(&script1)?;
let ps = ProcState::build(flags).await?; let ps = ProcState::build(flags).await?;
let handler = Arc::new(Mutex::new(FetchHandler::new( let mut cache = cache::FetchCacher::new(
&ps, ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
Permissions::allow_all(), Permissions::allow_all(),
Permissions::allow_all(), Permissions::allow_all(),
)?));
let mut builder = module_graph::GraphBuilder::new(
handler,
ps.maybe_import_map.clone(),
ps.lockfile.clone(),
); );
builder.add(&main_module, false).await?; let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
builder.analyze_config_file(&ps.maybe_config_file).await?; let maybe_imports = ps
let module_graph = builder.get_graph(); .maybe_config_file
.as_ref()
.map(|cf| cf.to_maybe_imports())
.flatten();
let maybe_resolver =
ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
let graph = deno_graph::create_graph(
vec![main_module.clone()],
false,
maybe_imports,
&mut cache,
maybe_resolver.as_ref().map(|r| r.as_resolver()),
maybe_locker,
None,
)
.await;
graph.valid()?;
// Find all local files in graph // Find all local files in graph
let mut paths_to_watch: Vec<PathBuf> = module_graph let mut paths_to_watch: Vec<PathBuf> = graph
.get_modules() .specifiers()
.iter() .iter()
.filter_map(|specifier| specifier.to_file_path().ok()) .filter_map(|(_, r)| {
r.as_ref()
.ok()
.map(|(s, _)| s.to_file_path().ok())
.flatten()
})
.collect(); .collect();
if let Some(import_map) = ps.flags.import_map_path.as_ref() { if let Some(import_map) = ps.flags.import_map_path.as_ref() {
@ -948,10 +1020,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
main_module: &ModuleSpecifier, main_module: &ModuleSpecifier,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
if self.compat { if self.compat {
self self.worker.execute_side_module(&compat::GLOBAL_URL).await?;
.worker
.execute_side_module(&compat::get_node_globals_url())
.await?;
} }
self.worker.execute_main_module(main_module).await?; self.worker.execute_main_module(main_module).await?;
self.worker.execute_script( self.worker.execute_script(
@ -1046,9 +1115,7 @@ async fn run_command(
debug!("main_module {}", main_module); debug!("main_module {}", main_module);
if flags.compat { if flags.compat {
worker worker.execute_side_module(&compat::GLOBAL_URL).await?;
.execute_side_module(&compat::get_node_globals_url())
.await?;
} }
worker.execute_main_module(&main_module).await?; worker.execute_main_module(&main_module).await?;
worker.execute_script( worker.execute_script(

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,8 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::module_graph::TypeLib; use crate::emit::TypeLib;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt; use deno_core::futures::future::FutureExt;
use deno_core::futures::Future; use deno_core::futures::Future;
@ -10,16 +11,12 @@ use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use import_map::ImportMap;
use std::cell::RefCell; use std::cell::RefCell;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::str; use std::str;
pub struct CliModuleLoader { pub(crate) struct CliModuleLoader {
/// When flags contains a `.import_map_path` option, the content of the
/// import map file will be resolved and set.
pub import_map: Option<ImportMap>,
pub lib: TypeLib, pub lib: TypeLib,
/// The initial set of permissions used to resolve the static imports in the /// The initial set of permissions used to resolve the static imports in the
/// worker. They are decoupled from the worker (dynamic) permissions since /// worker. They are decoupled from the worker (dynamic) permissions since
@ -36,10 +33,7 @@ impl CliModuleLoader {
TypeLib::DenoWindow TypeLib::DenoWindow
}; };
let import_map = ps.maybe_import_map.clone();
Rc::new(CliModuleLoader { Rc::new(CliModuleLoader {
import_map,
lib, lib,
root_permissions: Permissions::allow_all(), root_permissions: Permissions::allow_all(),
ps, ps,
@ -54,7 +48,6 @@ impl CliModuleLoader {
}; };
Rc::new(CliModuleLoader { Rc::new(CliModuleLoader {
import_map: None,
lib, lib,
root_permissions: permissions, root_permissions: permissions,
ps, ps,
@ -67,44 +60,25 @@ impl ModuleLoader for CliModuleLoader {
&self, &self,
specifier: &str, specifier: &str,
referrer: &str, referrer: &str,
is_main: bool, _is_main: bool,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<ModuleSpecifier, AnyError> {
// FIXME(bartlomieju): hacky way to provide compatibility with repl self.ps.resolve(specifier, referrer)
let referrer = if referrer.is_empty() && self.ps.flags.repl {
deno_core::DUMMY_SPECIFIER
} else {
referrer
};
// TODO(ry) I think we can remove this conditional. At the time of writing
// we don't have any tests that fail if it was removed.
// https://github.com/WICG/import-maps/issues/157
if !is_main {
if let Some(import_map) = &self.import_map {
return import_map
.resolve(specifier, referrer)
.map_err(AnyError::from);
}
}
let module_specifier = deno_core::resolve_import(specifier, referrer)?;
Ok(module_specifier)
} }
fn load( fn load(
&self, &self,
module_specifier: &ModuleSpecifier, module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>, maybe_referrer: Option<ModuleSpecifier>,
_is_dynamic: bool, is_dynamic: bool,
) -> Pin<Box<deno_core::ModuleSourceFuture>> { ) -> Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.clone(); let module_specifier = module_specifier.clone();
let ps = self.ps.clone(); let ps = self.ps.clone();
// NOTE: this block is async only because of `deno_core` // NOTE: this block is async only because of `deno_core` interface
// interface requirements; module was already loaded // requirements; module was already loaded when constructing module graph
// when constructing module graph during call to `prepare_load`. // during call to `prepare_load`.
async move { ps.load(module_specifier, maybe_referrer) }.boxed_local() async move { ps.load(module_specifier, maybe_referrer, is_dynamic) }
.boxed_local()
} }
fn prepare_load( fn prepare_load(
@ -117,24 +91,31 @@ impl ModuleLoader for CliModuleLoader {
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> { ) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
let specifier = specifier.clone(); let specifier = specifier.clone();
let ps = self.ps.clone(); let ps = self.ps.clone();
let maybe_import_map = self.import_map.clone();
let state = op_state.borrow(); let state = op_state.borrow();
let root_permissions = self.root_permissions.clone();
let dynamic_permissions = state.borrow::<Permissions>().clone(); let dynamic_permissions = state.borrow::<Permissions>().clone();
let root_permissions = if is_dynamic {
dynamic_permissions.clone()
} else {
self.root_permissions.clone()
};
let lib = self.lib.clone(); let lib = match self.lib {
TypeLib::DenoWindow => crate::emit::TypeLib::DenoWindow,
TypeLib::DenoWorker => crate::emit::TypeLib::DenoWorker,
TypeLib::UnstableDenoWindow => crate::emit::TypeLib::UnstableDenoWindow,
TypeLib::UnstableDenoWorker => crate::emit::TypeLib::UnstableDenoWorker,
};
drop(state); drop(state);
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param // TODO(bartlomieju): `prepare_module_load` should take `load_id` param
async move { async move {
ps.prepare_module_load( ps.prepare_module_load(
specifier, vec![specifier],
is_dynamic,
lib, lib,
root_permissions, root_permissions,
dynamic_permissions, dynamic_permissions,
is_dynamic,
maybe_import_map,
) )
.await .await
} }

View file

@ -1,27 +1,30 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::module_graph::BundleType; use crate::cache;
use crate::module_graph::EmitOptions; use crate::config_file::IgnoredCompilerOptions;
use crate::module_graph::GraphBuilder; use crate::diagnostics::Diagnostics;
use crate::emit;
use crate::errors::get_error_class_name;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::specifier_handler::FetchHandler; use crate::resolver::ImportMapResolver;
use crate::specifier_handler::MemoryHandler;
use crate::specifier_handler::SpecifierHandler;
use deno_core::error::custom_error;
use deno_core::error::generic_error; use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::error::Context; use deno_core::error::Context;
use deno_core::parking_lot::Mutex;
use deno_core::resolve_url_or_path; use deno_core::resolve_url_or_path;
use deno_core::serde_json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
use deno_graph;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use import_map::ImportMap; use import_map::ImportMap;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@ -37,6 +40,15 @@ enum RuntimeBundleType {
Classic, Classic,
} }
impl<'a> From<&'a RuntimeBundleType> for emit::BundleType {
fn from(bundle_type: &'a RuntimeBundleType) -> Self {
match bundle_type {
RuntimeBundleType::Classic => Self::Classic,
RuntimeBundleType::Module => Self::Module,
}
}
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct EmitArgs { struct EmitArgs {
@ -52,10 +64,21 @@ struct EmitArgs {
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct EmitResult { struct EmitResult {
diagnostics: crate::diagnostics::Diagnostics, diagnostics: Diagnostics,
files: HashMap<String, String>, files: HashMap<String, String>,
ignored_options: Option<crate::config_file::IgnoredCompilerOptions>, #[serde(rename = "ignoredOptions")]
stats: crate::module_graph::Stats, maybe_ignored_options: Option<IgnoredCompilerOptions>,
stats: emit::Stats,
}
fn to_maybe_imports(
referrer: &ModuleSpecifier,
maybe_options: Option<&HashMap<String, Value>>,
) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
let options = maybe_options.as_ref()?;
let types_value = options.get("types")?;
let types: Vec<String> = serde_json::from_value(types_value.clone()).ok()?;
Some(vec![(referrer.clone(), types)])
} }
async fn op_emit( async fn op_emit(
@ -65,23 +88,19 @@ async fn op_emit(
) -> Result<EmitResult, AnyError> { ) -> Result<EmitResult, AnyError> {
deno_runtime::ops::check_unstable2(&state, "Deno.emit"); deno_runtime::ops::check_unstable2(&state, "Deno.emit");
let root_specifier = args.root_specifier; let root_specifier = args.root_specifier;
let ps = state.borrow().borrow::<ProcState>().clone(); let state = state.borrow();
let mut runtime_permissions = { let ps = state.borrow::<ProcState>();
let state = state.borrow(); let mut runtime_permissions = { state.borrow::<Permissions>().clone() };
state.borrow::<Permissions>().clone() let mut cache: Box<dyn cache::CacherLoader> =
}; if let Some(sources) = &args.sources {
// when we are actually resolving modules without provided sources, we should Box::new(cache::MemoryCacher::new(sources.clone()))
// treat the root module as a dynamic import so that runtime permissions are
// applied.
let handler: Arc<Mutex<dyn SpecifierHandler>> =
if let Some(sources) = args.sources {
Arc::new(Mutex::new(MemoryHandler::new(sources)))
} else { } else {
Arc::new(Mutex::new(FetchHandler::new( Box::new(cache::FetchCacher::new(
&ps, ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
runtime_permissions.clone(), runtime_permissions.clone(),
runtime_permissions.clone(), runtime_permissions.clone(),
)?)) ))
}; };
let maybe_import_map = if let Some(import_map_str) = args.import_map_path { let maybe_import_map = if let Some(import_map_str) = args.import_map_path {
let import_map_specifier = resolve_url_or_path(&import_map_str) let import_map_specifier = resolve_url_or_path(&import_map_str)
@ -107,37 +126,125 @@ async fn op_emit(
} else { } else {
None None
}; };
let mut builder = GraphBuilder::new(handler, maybe_import_map, None); let roots = vec![resolve_url_or_path(&root_specifier)?];
let root_specifier = resolve_url_or_path(&root_specifier)?; let maybe_imports =
builder.add(&root_specifier, false).await.map_err(|_| { to_maybe_imports(&roots[0], args.compiler_options.as_ref());
type_error(format!( let maybe_resolver = maybe_import_map.as_ref().map(ImportMapResolver::new);
"Unable to handle the given specifier: {}", let graph = Arc::new(
&root_specifier deno_graph::create_graph(
)) roots,
})?; true,
builder maybe_imports,
.analyze_compiler_options(&args.compiler_options) cache.as_mut_loader(),
.await?; maybe_resolver.as_ref().map(|r| r.as_resolver()),
let bundle_type = match args.bundle { None,
Some(RuntimeBundleType::Module) => BundleType::Module, None,
Some(RuntimeBundleType::Classic) => BundleType::Classic, )
None => BundleType::None, .await,
}; );
let graph = builder.get_graph(); // There are certain graph errors that we want to return as an error of an op,
// versus something that gets returned as a diagnostic of the op, this is
// handled here.
if let Err(err) = graph.valid() {
let err: AnyError = err.into();
if get_error_class_name(&err) == "PermissionDenied" {
return Err(err);
}
}
let check = args.check.unwrap_or(true);
let debug = ps.flags.log_level == Some(log::Level::Debug); let debug = ps.flags.log_level == Some(log::Level::Debug);
let graph_errors = graph.get_errors(); let tsc_emit = check && args.bundle.is_none();
let (files, mut result_info) = graph.emit(EmitOptions { let (ts_config, maybe_ignored_options) = emit::get_ts_config(
bundle_type, emit::ConfigType::RuntimeEmit { tsc_emit },
check: args.check.unwrap_or(true), None,
debug, args.compiler_options.as_ref(),
maybe_user_config: args.compiler_options, )?;
})?; let (files, mut diagnostics, stats) = if check && args.bundle.is_none() {
result_info.diagnostics.extend_graph_errors(graph_errors); let (diagnostics, stats) = if args.sources.is_none()
&& emit::valid_emit(
graph.as_ref(),
cache.as_cacher(),
&ts_config,
ps.flags.reload,
&HashSet::default(),
) {
log::debug!(
"cache is valid for \"{}\", skipping check/emit",
root_specifier
);
(Diagnostics::default(), emit::Stats::default())
} else {
let emit_result = emit::check_and_maybe_emit(
graph.clone(),
cache.as_mut_cacher(),
emit::CheckOptions {
debug,
emit_with_diagnostics: true,
maybe_config_specifier: None,
ts_config,
},
)?;
(emit_result.diagnostics, emit_result.stats)
};
let files = emit::to_file_map(graph.as_ref(), cache.as_mut_cacher());
(files, diagnostics, stats)
} else if let Some(bundle) = &args.bundle {
let (diagnostics, stats) = if check {
if ts_config.get_declaration() {
return Err(custom_error("TypeError", "The bundle option is set, but the compiler option of `declaration` is true which is not currently supported."));
}
let emit_result = emit::check_and_maybe_emit(
graph.clone(),
cache.as_mut_cacher(),
emit::CheckOptions {
debug,
emit_with_diagnostics: true,
maybe_config_specifier: None,
ts_config: ts_config.clone(),
},
)?;
(emit_result.diagnostics, emit_result.stats)
} else {
(Diagnostics::default(), Default::default())
};
let (emit, maybe_map) = emit::bundle(
graph.as_ref(),
emit::BundleOptions {
bundle_type: bundle.into(),
ts_config,
},
)?;
let mut files = HashMap::new();
files.insert("deno:///bundle.js".to_string(), emit);
if let Some(map) = maybe_map {
files.insert("deno:///bundle.js.map".to_string(), map);
}
(files, diagnostics, stats)
} else {
if ts_config.get_declaration() {
return Err(custom_error("TypeError", "The option of `check` is false, but the compiler option of `declaration` is true which is not currently supported."));
}
let emit_result = emit::emit(
graph.as_ref(),
cache.as_mut_cacher(),
emit::EmitOptions {
reload: ps.flags.reload,
ts_config,
reload_exclusions: HashSet::default(),
},
)?;
let files = emit::to_file_map(graph.as_ref(), cache.as_mut_cacher());
(files, emit_result.diagnostics, emit_result.stats)
};
// we want to add any errors that were returned as an `Err` earlier by adding
// them to the diagnostics.
diagnostics.extend_graph_errors(graph.errors());
Ok(EmitResult { Ok(EmitResult {
diagnostics: result_info.diagnostics, diagnostics,
files, files,
ignored_options: result_info.maybe_ignored_options, maybe_ignored_options,
stats: result_info.stats, stats,
}) })
} }

View file

@ -1,23 +1,24 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::cache;
use crate::colors; use crate::colors;
use crate::compat; use crate::compat;
use crate::config_file::ConfigFile; use crate::config_file::ConfigFile;
use crate::deno_dir; use crate::deno_dir;
use crate::emit;
use crate::errors::get_module_graph_error_class;
use crate::file_fetcher::CacheSetting; use crate::file_fetcher::CacheSetting;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
use crate::flags; use crate::flags;
use crate::http_cache; use crate::http_cache;
use crate::lockfile::as_maybe_locker;
use crate::lockfile::Lockfile; use crate::lockfile::Lockfile;
use crate::module_graph::CheckOptions; use crate::resolver::ImportMapResolver;
use crate::module_graph::GraphBuilder;
use crate::module_graph::TranspileOptions;
use crate::module_graph::TypeLib;
use crate::source_maps::SourceMapGetter; use crate::source_maps::SourceMapGetter;
use crate::specifier_handler::FetchHandler;
use crate::version; use crate::version;
use deno_core::error::anyhow; use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::get_custom_error_class; use deno_core::error::get_custom_error_class;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::error::Context; 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::rustls_native_certs::load_native_certs;
use deno_tls::webpki_roots::TLS_SERVER_ROOTS; use deno_tls::webpki_roots::TLS_SERVER_ROOTS;
use import_map::ImportMap; use import_map::ImportMap;
use log::debug;
use log::info;
use log::warn;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
@ -59,12 +57,25 @@ pub struct Inner {
pub dir: deno_dir::DenoDir, pub dir: deno_dir::DenoDir,
pub coverage_dir: Option<String>, pub coverage_dir: Option<String>,
pub file_fetcher: FileFetcher, pub file_fetcher: FileFetcher,
pub modules: modules: Arc<Mutex<HashMap<ModuleSpecifier, Result<ModuleSource, AnyError>>>>,
Arc<Mutex<HashMap<ModuleSpecifier, Result<ModuleSource, AnyError>>>>,
pub lockfile: Option<Arc<Mutex<Lockfile>>>, pub lockfile: Option<Arc<Mutex<Lockfile>>>,
pub maybe_config_file: Option<ConfigFile>, pub maybe_config_file: Option<ConfigFile>,
pub maybe_import_map: Option<ImportMap>, pub maybe_import_map: Option<ImportMap>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>, 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 root_cert_store: Option<RootCertStore>,
pub blob_store: BlobStore, pub blob_store: BlobStore,
pub broadcast_channel: InMemoryBroadcastChannel, pub broadcast_channel: InMemoryBroadcastChannel,
@ -222,11 +233,11 @@ impl ProcState {
let diagnostics = import_map.update_imports(node_builtins)?; let diagnostics = import_map.update_imports(node_builtins)?;
if !diagnostics.is_empty() { 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 { 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); maybe_import_map = Some(import_map);
@ -252,6 +263,9 @@ impl ProcState {
maybe_config_file, maybe_config_file,
maybe_import_map, maybe_import_map,
maybe_inspector_server, maybe_inspector_server,
maybe_graph_error: Default::default(),
resolution_map: Default::default(),
resolved_map: Default::default(),
root_cert_store: Some(root_cert_store.clone()), root_cert_store: Some(root_cert_store.clone()),
blob_store, blob_store,
broadcast_channel, broadcast_channel,
@ -260,72 +274,174 @@ impl ProcState {
}))) })))
} }
/// Prepares a set of module specifiers for loading in one shot. /// Return any imports that should be brought into the scope of the module
pub async fn prepare_module_graph( /// 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, &self,
specifiers: Vec<ModuleSpecifier>, roots: Vec<ModuleSpecifier>,
lib: TypeLib, is_dynamic: bool,
lib: emit::TypeLib,
root_permissions: Permissions, root_permissions: Permissions,
dynamic_permissions: Permissions, dynamic_permissions: Permissions,
maybe_import_map: Option<ImportMap>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let handler = Arc::new(Mutex::new(FetchHandler::new( let mut cache = cache::FetchCacher::new(
self, self.dir.gen_cache.clone(),
root_permissions, self.file_fetcher.clone(),
dynamic_permissions, 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);
let mut builder = // Determine any modules that have already been emitted this session and
GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone()); // should be skipped.
let reload_exclusions: HashSet<ModuleSpecifier> = {
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 modules = self.modules.lock(); let modules = self.modules.lock();
modules.keys().cloned().collect::<HashSet<_>>() modules.keys().cloned().collect()
}; };
let result_modules = if self.flags.no_check { let config_type = if self.flags.no_check {
let result_info = graph.transpile(TranspileOptions { emit::ConfigType::Emit
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
} else { } else {
let result_info = graph.check(CheckOptions { emit::ConfigType::Check {
debug, tsc_emit: true,
emit: true,
lib, 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(); let (ts_config, maybe_ignored_options) =
loadable_modules.extend(result_modules); 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 { if let Some(ref lockfile) = self.lockfile {
let g = lockfile.lock(); let g = lockfile.lock();
g.write()?; g.write()?;
@ -334,127 +450,116 @@ impl ProcState {
Ok(()) Ok(())
} }
/// This function is called when new module load is initialized by the JsRuntime. Its pub(crate) fn resolve(
/// resposibility is to collect all dependencies and if it is required then also perform TS
/// typecheck and traspilation.
pub async fn prepare_module_load(
&self, &self,
specifier: ModuleSpecifier, specifier: &str,
lib: TypeLib, referrer: &str,
root_permissions: Permissions, ) -> Result<ModuleSpecifier, AnyError> {
dynamic_permissions: Permissions, let resolution_map = self.resolution_map.lock();
is_dynamic: bool, if let Some((_, Some(map))) = deno_core::resolve_url_or_path(referrer)
maybe_import_map: Option<ImportMap>, .ok()
) -> Result<(), AnyError> { .map(|s| (s.clone(), resolution_map.get(&s)))
let specifier = specifier.clone(); {
let handler = Arc::new(Mutex::new(FetchHandler::new( if let Some(resolved) = map.get(specifier) {
self, match resolved {
root_permissions, Some(Ok((specifier, span))) => {
dynamic_permissions, let mut resolved_map = self.resolved_map.lock();
)?)); resolved_map.insert(specifier.clone(), span.clone());
let mut builder = return Ok(specifier.clone());
GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone()); }
if self.flags.compat { Some(Err(err)) => {
builder.add(&compat::get_node_globals_url(), false).await?; return Err(custom_error(
} "TypeError",
builder.add(&specifier, is_dynamic).await?; format!("{}\n", err.to_string_with_span()),
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);
} }
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 { } else {
let result_info = graph.check(CheckOptions { referrer
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
}; };
if let Some(import_map) = &self.maybe_import_map {
let mut loadable_modules = self.modules.lock(); import_map
loadable_modules.extend(result_modules); .resolve(specifier, referrer)
.map_err(|err| err.into())
if let Some(ref lockfile) = self.lockfile { } else {
let g = lockfile.lock(); deno_core::resolve_import(specifier, referrer).map_err(|err| err.into())
g.write()?;
} }
Ok(())
} }
pub fn load( pub fn load(
&self, &self,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>, maybe_referrer: Option<ModuleSpecifier>,
is_dynamic: bool,
) -> Result<ModuleSource, AnyError> { ) -> 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(); let modules = self.modules.lock();
modules modules
.get(&specifier) .get(&specifier)
.map(|r| match r { .map(|r| match r {
Ok(module_source) => Ok(module_source.clone()), Ok(module_source) => Ok(module_source.clone()),
Err(err) => { Err(err) => {
// TODO(@kitsonk) this feels a bit hacky but it works, without // this is the "pending" error we will return
// introducing another enum to have to try to deal with. let err = if let Some(error_class) = get_custom_error_class(err) {
if get_custom_error_class(err) == Some("NotFound") { if error_class == "NotFound" && maybe_referrer.is_some() && !is_dynamic {
let message = if let Some(referrer) = &maybe_referrer { let resolved_map = self.resolved_map.lock();
format!("{}\n From: {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err, referrer) // in situations where we were to try to load a module that wasn't
} else { // emitted and we can't run the original source code (it isn't)
format!("{}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err) // JavaScript, we will load a blank module instead. This is
}; // usually caused by people exporting type only exports and not
warn!("{}: {}", crate::colors::yellow("warning"), message); // type checking.
Ok(ModuleSource { if let Some(span) = resolved_map.get(&specifier) {
code: "".to_string(), 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);
module_url_found: specifier.to_string(), return Ok(ModuleSource {
module_url_specified: specifier.to_string(), code: "".to_string(),
}) module_url_found: specifier.to_string(),
module_url_specified: specifier.to_string(),
});
}
}
custom_error(error_class, err.to_string())
} else { } else {
// anyhow errors don't support cloning, so we have to manage this anyhow!(err.to_string())
// ourselves };
Err(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 {
Err(err)
} }
}, }
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
if let Some(referrer) = maybe_referrer { if maybe_referrer.is_some() && !is_dynamic {
Err(anyhow!( let resolved_map = self.resolved_map.lock();
"Module \"{}\" is missing from the graph.\n From: {}", if let Some(span) = resolved_map.get(&specifier) {
specifier, return Err(custom_error("NotFound", format!("Cannot load module \"{}\".\n at {}", specifier, span)));
referrer }
))
} else {
Err(anyhow!(
"Module \"{}\" is missing from the graph.",
specifier
))
} }
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) { if let Some((code, maybe_map)) = self.get_emit(&specifier) {
let code = String::from_utf8(code).unwrap(); let code = String::from_utf8(code).unwrap();
source_map_from_code(code).or(maybe_map) 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) source_map_from_code(source.code)
} else { } else {
None None

35
cli/resolver.rs Normal file
View file

@ -0,0 +1,35 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_core::ModuleSpecifier;
use deno_graph::source::Resolver;
use import_map::ImportMap;
/// Wraps an import map to be used when building a deno_graph module graph.
/// This is done to avoid having `import_map` be a direct dependency of
/// `deno_graph`.
#[derive(Debug)]
pub(crate) struct ImportMapResolver<'a>(&'a ImportMap);
impl<'a> ImportMapResolver<'a> {
pub fn new(import_map: &'a ImportMap) -> Self {
Self(import_map)
}
pub fn as_resolver(&'a self) -> &'a dyn Resolver {
self
}
}
impl Resolver for ImportMapResolver<'_> {
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
self
.0
.resolve(specifier, referrer.as_str())
.map_err(|err| err.into())
}
}

View file

@ -1,776 +0,0 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ast::Location;
use crate::disk_cache::DiskCache;
use crate::file_fetcher::FileFetcher;
use crate::proc_state::ProcState;
use deno_ast::MediaType;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::futures::future;
use deno_core::futures::Future;
use deno_core::futures::FutureExt;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json;
use deno_core::ModuleSpecifier;
use deno_runtime::permissions::Permissions;
use log::debug;
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
pub type DependencyMap = HashMap<String, Dependency>;
type FetchFutureOutput = Result<CachedModule, (ModuleSpecifier, AnyError)>;
pub type FetchFuture = Pin<Box<dyn Future<Output = FetchFutureOutput> + Send>>;
/// A group of errors that represent errors that can occur with an
/// an implementation of `SpecifierHandler`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum HandlerError {
/// A fetch error, where we have a location associated with it.
FetchErrorWithLocation(String, Location),
}
impl fmt::Display for HandlerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
HandlerError::FetchErrorWithLocation(ref err, ref location) => {
write!(f, "{}\n at {}", err, location)
}
}
}
}
impl std::error::Error for HandlerError {}
#[derive(Debug, Clone)]
pub struct CachedModule {
pub is_remote: bool,
pub maybe_dependencies: Option<DependencyMap>,
pub maybe_emit: Option<Emit>,
pub maybe_emit_path: Option<(PathBuf, Option<PathBuf>)>,
pub maybe_types: Option<String>,
pub maybe_version: Option<String>,
pub media_type: MediaType,
pub requested_specifier: ModuleSpecifier,
pub source: Arc<String>,
pub source_path: PathBuf,
pub specifier: ModuleSpecifier,
}
impl Default for CachedModule {
fn default() -> Self {
let specifier = deno_core::resolve_url("file:///example.js").unwrap();
CachedModule {
is_remote: false,
maybe_dependencies: None,
maybe_emit: None,
maybe_emit_path: None,
maybe_types: None,
maybe_version: None,
media_type: MediaType::Unknown,
requested_specifier: specifier.clone(),
source: Arc::new(String::default()),
source_path: PathBuf::new(),
specifier,
}
}
}
/// An enum to own the a specific emit.
///
/// Currently there is only one type of emit that is cacheable, but this has
/// been added to future proof the ability for the specifier handler
/// implementations to be able to handle other types of emits, like form a
/// runtime API which might have a different configuration.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Emit {
/// Code that was emitted for use by the CLI
Cli((String, Option<String>)),
}
impl Default for Emit {
fn default() -> Self {
Emit::Cli(("".to_string(), None))
}
}
#[derive(Debug, Clone)]
pub struct Dependency {
/// Flags if the dependency is a dynamic import or not.
pub is_dynamic: bool,
/// The location in the source code where the dependency statement occurred.
pub location: Location,
/// The module specifier that resolves to the runtime code dependency for the
/// module.
pub maybe_code: Option<ModuleSpecifier>,
/// The module specifier that resolves to the type only dependency for the
/// module.
pub maybe_type: Option<ModuleSpecifier>,
}
impl Dependency {
pub fn new(location: Location) -> Self {
Dependency {
is_dynamic: false,
location,
maybe_code: None,
maybe_type: None,
}
}
}
pub trait SpecifierHandler: Sync + Send {
/// Instructs the handler to fetch a specifier or retrieve its value from the
/// cache.
fn fetch(
&mut self,
specifier: ModuleSpecifier,
maybe_location: Option<Location>,
is_dynamic: bool,
) -> FetchFuture;
/// Get the optional build info from the cache for a given module specifier.
/// Because build infos are only associated with the "root" modules, they are
/// not expected to be cached for each module, but are "lazily" checked when
/// a root module is identified. The `emit_type` also indicates what form
/// of the module the build info is valid for.
fn get_tsbuildinfo(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError>;
/// Set the emit for the module specifier.
fn set_cache(
&mut self,
specifier: &ModuleSpecifier,
emit: &Emit,
) -> Result<(), AnyError>;
/// When parsed out of a JavaScript module source, the triple slash reference
/// to the types should be stored in the cache.
fn set_types(
&mut self,
specifier: &ModuleSpecifier,
types: String,
) -> Result<(), AnyError>;
/// Set the build info for a module specifier, also providing the cache type.
fn set_tsbuildinfo(
&mut self,
specifier: &ModuleSpecifier,
tsbuildinfo: String,
) -> Result<(), AnyError>;
/// Set the graph dependencies for a given module specifier.
fn set_deps(
&mut self,
specifier: &ModuleSpecifier,
dependencies: DependencyMap,
) -> Result<(), AnyError>;
/// Set the version of the source for a given module, which is used to help
/// determine if a module needs to be re-emitted.
fn set_version(
&mut self,
specifier: &ModuleSpecifier,
version: String,
) -> Result<(), AnyError>;
}
impl fmt::Debug for dyn SpecifierHandler {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SpecifierHandler {{ }}")
}
}
/// A representation of meta data for a compiled file.
///
/// *Note* this is currently just a copy of what is located in `tsc.rs` but will
/// be refactored to be able to store dependencies and type information in the
/// future.
#[derive(Deserialize, Serialize)]
pub struct CompiledFileMetadata {
pub version_hash: String,
}
impl CompiledFileMetadata {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AnyError> {
let metadata_string = std::str::from_utf8(bytes)?;
serde_json::from_str::<Self>(metadata_string).map_err(|e| e.into())
}
pub fn to_json_string(&self) -> Result<String, AnyError> {
serde_json::to_string(self).map_err(|e| e.into())
}
}
/// An implementation of the `SpecifierHandler` trait that integrates with the
/// existing `file_fetcher` interface, which will eventually be refactored to
/// align it more to the `SpecifierHandler` trait.
pub struct FetchHandler {
/// An instance of disk where generated (emitted) files are stored.
disk_cache: DiskCache,
/// The set permissions which are used for root modules (static imports).
root_permissions: Permissions,
/// The set of permissions which are used for dynamic imports.
dynamic_permissions: Permissions,
/// A clone of the `ps` file fetcher.
file_fetcher: FileFetcher,
}
impl FetchHandler {
pub fn new(
ps: &ProcState,
root_permissions: Permissions,
dynamic_permissions: Permissions,
) -> Result<Self, AnyError> {
let disk_cache = ps.dir.gen_cache.clone();
let file_fetcher = ps.file_fetcher.clone();
Ok(FetchHandler {
disk_cache,
root_permissions,
dynamic_permissions,
file_fetcher,
})
}
}
impl SpecifierHandler for FetchHandler {
fn fetch(
&mut self,
requested_specifier: ModuleSpecifier,
maybe_location: Option<Location>,
is_dynamic: bool,
) -> FetchFuture {
// When the module graph fetches dynamic modules, the set of dynamic
// permissions need to be applied. Other static imports have all
// permissions.
let mut permissions = if is_dynamic {
self.dynamic_permissions.clone()
} else {
self.root_permissions.clone()
};
let file_fetcher = self.file_fetcher.clone();
let disk_cache = self.disk_cache.clone();
async move {
let source_file = file_fetcher
.fetch(&requested_specifier, &mut permissions)
.await
.map_err(|err| {
let err = if let Some(e) = err.downcast_ref::<std::io::Error>() {
if e.kind() == std::io::ErrorKind::NotFound {
let message = if let Some(location) = &maybe_location {
format!(
"Cannot resolve module \"{}\" from \"{}\".",
requested_specifier, location.specifier
)
} else {
format!("Cannot resolve module \"{}\".", requested_specifier)
};
custom_error("NotFound", message)
} else {
err
}
} else {
err
};
if let Some(location) = maybe_location {
// Injected modules (like test and eval) come with locations, but
// they are confusing to the user to print out the location because
// they cannot actually get to the source code that is quoted, as
// it only exists in the runtime memory of Deno.
if !location.specifier.contains("$deno$") {
(
requested_specifier.clone(),
HandlerError::FetchErrorWithLocation(err.to_string(), location)
.into(),
)
} else {
(requested_specifier.clone(), err)
}
} else {
(requested_specifier.clone(), err)
}
})?;
let url = &source_file.specifier;
let is_remote = !(url.scheme() == "file"
|| url.scheme() == "data"
|| url.scheme() == "blob");
let filename = disk_cache.get_cache_filename_with_extension(url, "meta");
let maybe_version = if let Some(filename) = filename {
if let Ok(bytes) = disk_cache.get(&filename) {
if let Ok(compiled_file_metadata) =
CompiledFileMetadata::from_bytes(&bytes)
{
Some(compiled_file_metadata.version_hash)
} else {
None
}
} else {
None
}
} else {
None
};
let mut maybe_map_path = None;
let map_path =
disk_cache.get_cache_filename_with_extension(url, "js.map");
let maybe_map = if let Some(map_path) = map_path {
if let Ok(map) = disk_cache.get(&map_path) {
maybe_map_path = Some(disk_cache.location.join(map_path));
Some(String::from_utf8(map).unwrap())
} else {
None
}
} else {
None
};
let mut maybe_emit = None;
let mut maybe_emit_path = None;
let emit_path = disk_cache.get_cache_filename_with_extension(url, "js");
if let Some(emit_path) = emit_path {
if let Ok(code) = disk_cache.get(&emit_path) {
maybe_emit =
Some(Emit::Cli((String::from_utf8(code).unwrap(), maybe_map)));
maybe_emit_path =
Some((disk_cache.location.join(emit_path), maybe_map_path));
}
};
Ok(CachedModule {
is_remote,
maybe_dependencies: None,
maybe_emit,
maybe_emit_path,
maybe_types: source_file.maybe_types,
maybe_version,
media_type: source_file.media_type,
requested_specifier,
source: source_file.source,
source_path: source_file.local,
specifier: source_file.specifier,
})
}
.boxed()
}
fn get_tsbuildinfo(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "buildinfo");
if let Some(filename) = filename {
if let Ok(tsbuildinfo) = self.disk_cache.get(&filename) {
Ok(Some(String::from_utf8(tsbuildinfo)?))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn set_tsbuildinfo(
&mut self,
specifier: &ModuleSpecifier,
tsbuildinfo: String,
) -> Result<(), AnyError> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "buildinfo")
.unwrap();
debug!("set_tsbuildinfo - filename {:?}", filename);
self
.disk_cache
.set(&filename, tsbuildinfo.as_bytes())
.map_err(|e| e.into())
}
fn set_cache(
&mut self,
specifier: &ModuleSpecifier,
emit: &Emit,
) -> Result<(), AnyError> {
match emit {
Emit::Cli((code, maybe_map)) => {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "js")
.unwrap();
self.disk_cache.set(&filename, code.as_bytes())?;
if let Some(map) = maybe_map {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "js.map")
.unwrap();
self.disk_cache.set(&filename, map.as_bytes())?;
}
}
};
Ok(())
}
fn set_deps(
&mut self,
_specifier: &ModuleSpecifier,
_dependencies: DependencyMap,
) -> Result<(), AnyError> {
// file_fetcher doesn't have the concept of caching dependencies
Ok(())
}
fn set_types(
&mut self,
_specifier: &ModuleSpecifier,
_types: String,
) -> Result<(), AnyError> {
// file_fetcher doesn't have the concept of caching of the types
Ok(())
}
fn set_version(
&mut self,
specifier: &ModuleSpecifier,
version_hash: String,
) -> Result<(), AnyError> {
let compiled_file_metadata = CompiledFileMetadata { version_hash };
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "meta")
.unwrap();
self
.disk_cache
.set(
&filename,
compiled_file_metadata.to_json_string()?.as_bytes(),
)
.map_err(|e| e.into())
}
}
pub struct MemoryHandler {
sources: HashMap<String, Arc<String>>,
}
impl MemoryHandler {
pub fn new(sources: HashMap<String, Arc<String>>) -> Self {
Self { sources }
}
}
impl SpecifierHandler for MemoryHandler {
fn fetch(
&mut self,
specifier: ModuleSpecifier,
_maybe_referrer: Option<Location>,
_is_dynamic: bool,
) -> FetchFuture {
let mut specifier_text = specifier.to_string();
if !self.sources.contains_key(&specifier_text) {
specifier_text = specifier_text.replace("file:///", "/");
if !self.sources.contains_key(&specifier_text) {
// Convert `C:/a/path/file.ts` to `/a/path/file.ts`
specifier_text = specifier_text[3..].to_string()
}
}
let result = if let Some(source) = self.sources.get(&specifier_text) {
let media_type = MediaType::from(&specifier);
let is_remote = specifier.scheme() != "file";
Ok(CachedModule {
source: source.clone(),
requested_specifier: specifier.clone(),
specifier,
media_type,
is_remote,
..Default::default()
})
} else {
Err((
specifier.clone(),
custom_error(
"NotFound",
format!("Unable to find specifier in sources: {}", specifier),
),
))
};
Box::pin(future::ready(result))
}
fn get_tsbuildinfo(
&self,
_specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError> {
Ok(None)
}
fn set_cache(
&mut self,
_specifier: &ModuleSpecifier,
_emit: &Emit,
) -> Result<(), AnyError> {
Ok(())
}
fn set_types(
&mut self,
_specifier: &ModuleSpecifier,
_types: String,
) -> Result<(), AnyError> {
Ok(())
}
fn set_tsbuildinfo(
&mut self,
_specifier: &ModuleSpecifier,
_tsbuildinfo: String,
) -> Result<(), AnyError> {
Ok(())
}
fn set_deps(
&mut self,
_specifier: &ModuleSpecifier,
_dependencies: DependencyMap,
) -> Result<(), AnyError> {
Ok(())
}
fn set_version(
&mut self,
_specifier: &ModuleSpecifier,
_version: String,
) -> Result<(), AnyError> {
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::deno_dir::DenoDir;
use crate::file_fetcher::CacheSetting;
use crate::http_cache::HttpCache;
use deno_core::resolve_url_or_path;
use deno_runtime::deno_web::BlobStore;
use tempfile::TempDir;
macro_rules! map (
{ $($key:expr => $value:expr),+ } => {
{
let mut m = ::std::collections::HashMap::new();
$(
m.insert($key, $value);
)+
m
}
};
);
fn setup() -> (TempDir, FetchHandler) {
let temp_dir = TempDir::new().expect("could not setup");
let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()))
.expect("could not setup");
let file_fetcher = FileFetcher::new(
HttpCache::new(&temp_dir.path().to_path_buf().join("deps")),
CacheSetting::Use,
true,
None,
BlobStore::default(),
None,
)
.expect("could not setup");
let disk_cache = deno_dir.gen_cache;
let fetch_handler = FetchHandler {
disk_cache,
root_permissions: Permissions::allow_all(),
dynamic_permissions: Permissions::default(),
file_fetcher,
};
(temp_dir, fetch_handler)
}
#[tokio::test]
async fn test_fetch_handler_fetch() {
let _http_server_guard = test_util::http_server();
let (_, mut file_fetcher) = setup();
let specifier =
resolve_url_or_path("http://localhost:4545/subdir/mod2.ts").unwrap();
let cached_module: CachedModule = file_fetcher
.fetch(specifier.clone(), None, false)
.await
.unwrap();
assert!(cached_module.maybe_emit.is_none());
assert!(cached_module.maybe_dependencies.is_none());
assert_eq!(cached_module.media_type, MediaType::TypeScript);
assert_eq!(
cached_module.source.as_str(),
"export { printHello } from \"./print_hello.ts\";\n"
);
assert_eq!(cached_module.specifier, specifier);
}
#[tokio::test]
async fn test_fetch_handler_set_cache() {
let _http_server_guard = test_util::http_server();
let (_, mut file_fetcher) = setup();
let specifier =
resolve_url_or_path("http://localhost:4545/subdir/mod2.ts").unwrap();
let cached_module: CachedModule = file_fetcher
.fetch(specifier.clone(), None, false)
.await
.unwrap();
assert!(cached_module.maybe_emit.is_none());
let code = String::from("some code");
file_fetcher
.set_cache(&specifier, &Emit::Cli((code, None)))
.expect("could not set cache");
let cached_module: CachedModule = file_fetcher
.fetch(specifier.clone(), None, false)
.await
.unwrap();
assert_eq!(
cached_module.maybe_emit,
Some(Emit::Cli(("some code".to_string(), None)))
);
}
#[tokio::test]
async fn test_fetch_handler_is_remote() {
let _http_server_guard = test_util::http_server();
let (_, mut file_fetcher) = setup();
let specifier =
resolve_url_or_path("http://localhost:4545/subdir/mod2.ts").unwrap();
let cached_module: CachedModule =
file_fetcher.fetch(specifier, None, false).await.unwrap();
assert!(cached_module.is_remote);
let specifier = resolve_url_or_path(
test_util::testdata_path()
.join("subdir/mod1.ts")
.as_os_str()
.to_str()
.unwrap(),
)
.unwrap();
let cached_module: CachedModule =
file_fetcher.fetch(specifier, None, false).await.unwrap();
assert!(!cached_module.is_remote);
}
#[tokio::test]
async fn test_memory_handler_fetch() {
let a_src = r#"
import * as b from "./b.ts";
console.log(b);
"#;
let b_src = r#"
export const b = "b";
"#;
let c_src = r#"
export const c = "c";
"#;
let d_src = r#"
export const d: string;
"#;
let sources = map!(
"/a.ts" => a_src,
"/b.ts" => b_src,
"https://deno.land/x/c.js" => c_src,
"https://deno.land/x/d.d.ts" => d_src
);
let sources: HashMap<String, Arc<String>> = sources
.iter()
.map(|(k, v)| (k.to_string(), Arc::new(v.to_string())))
.collect();
let mut handler = MemoryHandler::new(sources);
let specifier = resolve_url_or_path("file:///a.ts").unwrap();
let actual: CachedModule = handler
.fetch(specifier.clone(), None, false)
.await
.expect("could not fetch module");
assert_eq!(actual.source.as_str(), a_src);
assert_eq!(actual.requested_specifier, specifier);
assert_eq!(actual.specifier, specifier);
assert_eq!(actual.media_type, MediaType::TypeScript);
assert!(!actual.is_remote);
let specifier = resolve_url_or_path("file:///b.ts").unwrap();
let actual: CachedModule = handler
.fetch(specifier.clone(), None, false)
.await
.expect("could not fetch module");
assert_eq!(actual.source.as_str(), b_src);
assert_eq!(actual.requested_specifier, specifier);
assert_eq!(actual.specifier, specifier);
assert_eq!(actual.media_type, MediaType::TypeScript);
assert!(!actual.is_remote);
let specifier = resolve_url_or_path("https://deno.land/x/c.js").unwrap();
let actual: CachedModule = handler
.fetch(specifier.clone(), None, false)
.await
.expect("could not fetch module");
assert_eq!(actual.source.as_str(), c_src);
assert_eq!(actual.requested_specifier, specifier);
assert_eq!(actual.specifier, specifier);
assert_eq!(actual.media_type, MediaType::JavaScript);
assert!(actual.is_remote);
let specifier = resolve_url_or_path("https://deno.land/x/d.d.ts").unwrap();
let actual: CachedModule = handler
.fetch(specifier.clone(), None, false)
.await
.expect("could not fetch module");
assert_eq!(actual.source.as_str(), d_src);
assert_eq!(actual.requested_specifier, specifier);
assert_eq!(actual.specifier, specifier);
assert_eq!(actual.media_type, MediaType::Dts);
assert!(actual.is_remote);
let specifier =
resolve_url_or_path("https://deno.land/x/missing.ts").unwrap();
handler
.fetch(specifier.clone(), None, false)
.await
.expect_err("should have errored");
let specifier = resolve_url_or_path("/a.ts").unwrap();
let actual: CachedModule = handler
.fetch(specifier.clone(), None, false)
.await
.expect("could not fetch module");
assert_eq!(actual.source.as_str(), a_src);
assert_eq!(actual.requested_specifier, specifier);
assert_eq!(actual.specifier, specifier);
assert_eq!(actual.media_type, MediaType::TypeScript);
assert!(!actual.is_remote);
let specifier = resolve_url_or_path("file:///C:/a.ts").unwrap();
let actual: CachedModule = handler
.fetch(specifier.clone(), None, false)
.await
.expect("could not fetch module");
assert_eq!(actual.source.as_str(), a_src);
assert_eq!(actual.requested_specifier, specifier);
assert_eq!(actual.specifier, specifier);
assert_eq!(actual.media_type, MediaType::TypeScript);
assert!(!actual.is_remote);
}
}

View file

@ -2761,7 +2761,6 @@ fn lsp_diagnostics_warn() {
.unwrap(); .unwrap();
assert!(maybe_err.is_none()); assert!(maybe_err.is_none());
assert!(maybe_res.is_some()); assert!(maybe_res.is_some());
let (method, _) = client.read_notification::<Value>().unwrap(); let (method, _) = client.read_notification::<Value>().unwrap();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _) = client.read_notification::<Value>().unwrap(); let (method, _) = client.read_notification::<Value>().unwrap();

View file

@ -206,6 +206,7 @@ fn bundle_js_watch() {
let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno);
std::thread::sleep(std::time::Duration::from_secs(1)); std::thread::sleep(std::time::Duration::from_secs(1));
assert_contains!(stderr_lines.next().unwrap(), "Check");
assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js"); assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js");
assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js");
let file = PathBuf::from(&bundle); let file = PathBuf::from(&bundle);
@ -214,6 +215,7 @@ fn bundle_js_watch() {
write(&file_to_watch, "console.log('Hello world2');").unwrap(); write(&file_to_watch, "console.log('Hello world2');").unwrap();
std::thread::sleep(std::time::Duration::from_secs(1)); std::thread::sleep(std::time::Duration::from_secs(1));
assert_contains!(stderr_lines.next().unwrap(), "Check");
assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); assert_contains!(stderr_lines.next().unwrap(), "File change detected!");
assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js"); assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js");
assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js");
@ -261,6 +263,7 @@ fn bundle_watch_not_exit() {
// Make sure the watcher actually restarts and works fine with the proper syntax // Make sure the watcher actually restarts and works fine with the proper syntax
write(&file_to_watch, "console.log(42);").unwrap(); write(&file_to_watch, "console.log(42);").unwrap();
std::thread::sleep(std::time::Duration::from_secs(1)); std::thread::sleep(std::time::Duration::from_secs(1));
assert_contains!(stderr_lines.next().unwrap(), "Check");
assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); assert_contains!(stderr_lines.next().unwrap(), "File change detected!");
assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js"); assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js");
assert_contains!(stderr_lines.next().unwrap(), "target.js"); assert_contains!(stderr_lines.next().unwrap(), "target.js");

View file

@ -1 +1,2 @@
[WILDCARD]
Module { isMod4: true } Module { isMod4: true }

View file

@ -1,53 +1,91 @@
{ {
"root": "file://[WILDCARD]/005_more_imports.ts", "roots": [
"file://[WILDCARD]/005_more_imports.ts"
],
"modules": [ "modules": [
{ {
"specifier": "file://[WILDCARD]/005_more_imports.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "./subdir/mod1.ts", "specifier": "./subdir/mod1.ts",
"code": "file://[WILDCARD]/subdir/mod1.ts" "code": {
"specifier": "file://[WILDCARD]/subdir/mod1.ts",
"span": {
"start": {
"line": 0,
"character": 52
},
"end": {
"line": 0,
"character": 70
}
}
}
} }
], ],
"size": 211,
"mediaType": "TypeScript",
"local": "[WILDCARD]005_more_imports.ts", "local": "[WILDCARD]005_more_imports.ts",
[WILDCARD] [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/005_more_imports.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/subdir/mod1.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "./subdir2/mod2.ts", "specifier": "./subdir2/mod2.ts",
"code": "file://[WILDCARD]/subdir/subdir2/mod2.ts" "code": {
"specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts",
"span": {
"start": {
"line": 0,
"character": 40
},
"end": {
"line": 0,
"character": 59
}
}
}
} }
], ],
"size": 308,
"mediaType": "TypeScript",
"local": "[WILDCARD]mod1.ts", "local": "[WILDCARD]mod1.ts",
[WILDCARD] [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/subdir/mod1.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/subdir/print_hello.ts",
"dependencies": [], "dependencies": [],
"size": 57,
"mediaType": "TypeScript",
"local": "[WILDCARD]print_hello.ts", "local": "[WILDCARD]print_hello.ts",
[WILDCARD] [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/subdir/print_hello.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "../print_hello.ts", "specifier": "../print_hello.ts",
"code": "file://[WILDCARD]/subdir/print_hello.ts" "code": {
"specifier": "file://[WILDCARD]/subdir/print_hello.ts",
"span": {
"start": {
"line": 0,
"character": 27
},
"end": {
"line": 0,
"character": 46
}
}
}
} }
], ],
"size": 157,
"mediaType": "TypeScript",
"local": "[WILDCARD]mod2.ts", "local": "[WILDCARD]mod2.ts",
[WILDCARD] [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts"
} }
], ],
"size": 733 "redirects": {}
} }

View file

@ -1,78 +1,164 @@
{ {
"root": "file://[WILDCARD]/076_info_json_deps_order.ts", "roots": [
"file://[WILDCARD]/076_info_json_deps_order.ts"
],
"modules": [ "modules": [
{ {
"specifier": "file://[WILDCARD]/076_info_json_deps_order.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "./recursive_imports/A.ts", "specifier": "./recursive_imports/A.ts",
"code": "file://[WILDCARD]/recursive_imports/A.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/A.ts",
"span": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 44
}
}
}
} }
], ],
"size": 81,
"mediaType": "TypeScript",
"local": "[WILDCARD]076_info_json_deps_order.ts", "local": "[WILDCARD]076_info_json_deps_order.ts",
"checksum": "5dd40fe33e5924cca513489ce568e86c9b9fe318a87975403c8923629018680d" [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/076_info_json_deps_order.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/recursive_imports/A.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "./B.ts", "specifier": "./B.ts",
"code": "file://[WILDCARD]/recursive_imports/B.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/B.ts",
"span": {
"start": {
"line": 0,
"character": 18
},
"end": {
"line": 0,
"character": 26
}
}
}
}, },
{ {
"specifier": "./common.ts", "specifier": "./common.ts",
"code": "file://[WILDCARD]/recursive_imports/common.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/common.ts",
"span": {
"start": {
"line": 1,
"character": 22
},
"end": {
"line": 1,
"character": 35
}
}
}
} }
], ],
"size": 108,
"mediaType": "TypeScript",
"local": "[WILDCARD]A.ts", "local": "[WILDCARD]A.ts",
"checksum": "3b45a105d892584298490cb73372b2cac57118e1e42a677a1d5cacea704d8d3a" [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/A.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/recursive_imports/B.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "./C.ts", "specifier": "./C.ts",
"code": "file://[WILDCARD]/recursive_imports/C.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/C.ts",
"span": {
"start": {
"line": 0,
"character": 18
},
"end": {
"line": 0,
"character": 26
}
}
}
}, },
{ {
"specifier": "./common.ts", "specifier": "./common.ts",
"code": "file://[WILDCARD]/recursive_imports/common.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/common.ts",
"span": {
"start": {
"line": 1,
"character": 22
},
"end": {
"line": 1,
"character": 35
}
}
}
} }
], ],
"size": 108,
"mediaType": "TypeScript",
"local": "[WILDCARD]B.ts", "local": "[WILDCARD]B.ts",
"checksum": "b12b0437ef9a91c4a4b1f66e8e4339f986b60bd8134031ccb296ce49df15b54e" [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/B.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/recursive_imports/C.ts",
"dependencies": [ "dependencies": [
{ {
"specifier": "./A.ts", "specifier": "./A.ts",
"code": "file://[WILDCARD]/recursive_imports/A.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/A.ts",
"span": {
"start": {
"line": 0,
"character": 18
},
"end": {
"line": 0,
"character": 26
}
}
}
}, },
{ {
"specifier": "./common.ts", "specifier": "./common.ts",
"code": "file://[WILDCARD]/recursive_imports/common.ts" "code": {
"specifier": "file://[WILDCARD]/recursive_imports/common.ts",
"span": {
"start": {
"line": 1,
"character": 22
},
"end": {
"line": 1,
"character": 35
}
}
}
} }
], ],
"size": 126,
"mediaType": "TypeScript",
"local": "[WILDCARD]C.ts", "local": "[WILDCARD]C.ts",
"checksum": "605875a410741bfaeeade28cbccf45f219ad99d987ea695e35eda75d2c53a658" [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/C.ts"
}, },
{ {
"specifier": "file://[WILDCARD]/recursive_imports/common.ts",
"dependencies": [], "dependencies": [],
"size": 28,
"mediaType": "TypeScript",
"local": "[WILDCARD]common.ts", "local": "[WILDCARD]common.ts",
"checksum": "c70025f0b936c02980c3be1fbd78f6f36b6241927c44ea67580821a6e664d8b3" [WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/common.ts"
} }
], ],
"size": 451 "redirects": {}
} }

View file

@ -1,4 +1,7 @@
[WILDCARD]error: Uncaught (in promise) TypeError: Relative import path "unmapped" not prefixed with / or ./ or ../ and not in import map from "[WILDCARD]" [WILDCARD]
error: Uncaught (in promise) TypeError: Relative import path "unmapped" not prefixed with / or ./ or ../ and not in import map from "file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts"
at file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:14
await import("unmapped"); await import("unmapped");
^ ^
at [WILDCARD] at async file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:1

View file

@ -1 +1,2 @@
[WILDCARD]
Hello Hello

View file

@ -1,2 +1,3 @@
DANGER: TLS certificate validation is disabled for all hostnames DANGER: TLS certificate validation is disabled for all hostnames
[WILDCARD]
Hello Hello

View file

@ -1,6 +1,7 @@
[WILDCARD] [WILDCARD]
Some Node built-ins were not added to the import map: Some Node built-ins were not added to the import map:
- "fs/promises" already exists and is mapped to "[WILDCARD]non_existent_file.js" - "fs/promises" already exists and is mapped to "file://[WILDCARD]/non_existent_file.js"
If you want to use Node built-ins provided by Deno remove listed specifiers from "imports" mapping in the import map file. If you want to use Node built-ins provided by Deno remove listed specifiers from "imports" mapping in the import map file.
[WILDCARD] [WILDCARD]
error: Cannot resolve module [WILDCARD] error: Cannot load module "file://[WILDCARD]/non_existent_file.js".
at file://[WILDCARD]/fs_promises.js:1:16

View file

@ -313,10 +313,9 @@ Deno.test({
Deno.test({ Deno.test({
name: "Deno.emit() - invalid syntax does not panic", name: "Deno.emit() - invalid syntax does not panic",
async fn() { async fn() {
await assertThrowsAsync(async () => { const { diagnostics } = await Deno.emit("/main.js", {
await Deno.emit("/main.js", { sources: {
sources: { "/main.js": `
"/main.js": `
export class Foo { export class Foo {
constructor() { constructor() {
console.log("foo"); console.log("foo");
@ -325,9 +324,14 @@ Deno.test({
console.log("bar"); console.log("bar");
} }
}`, }`,
}, },
});
}); });
assertEquals(diagnostics.length, 1);
assert(
diagnostics[0].messageText!.startsWith(
"The module's source code could not be parsed: Unexpected token `get`. Expected * for generator, private key, identifier or async at file:",
),
);
}, },
}); });
@ -356,12 +360,10 @@ Deno.test({
Deno.test({ Deno.test({
name: "Deno.emit() - Unknown media type does not panic", name: "Deno.emit() - Unknown media type does not panic",
async fn() { async fn() {
await assertThrowsAsync(async () => { await Deno.emit("https://example.com/foo", {
await Deno.emit("https://example.com/foo", { sources: {
sources: { "https://example.com/foo": `let foo: string = "foo";`,
"https://example.com/foo": `let foo: string = "foo";`, },
},
});
}); });
}, },
}); });
@ -487,7 +489,7 @@ Deno.test({
code: 900001, code: 900001,
start: null, start: null,
end: null, end: null,
messageText: "Unable to find specifier in sources: file:///b.ts", messageText: 'Cannot load module "file:///b.ts".',
messageChain: null, messageChain: null,
source: null, source: null,
sourceLine: null, sourceLine: null,
@ -497,7 +499,7 @@ Deno.test({
]); ]);
assert( assert(
Deno.formatDiagnostics(diagnostics).includes( Deno.formatDiagnostics(diagnostics).includes(
"Unable to find specifier in sources: file:///b.ts", 'Cannot load module "file:///b.ts".',
), ),
); );
}, },

View file

@ -1,6 +1,7 @@
[WILDCARD]Unsupported compiler options in "[WILDCARD]config.tsconfig.json". [WILDCARD]Unsupported compiler options in "[WILDCARD]config.tsconfig.json".
The following options were ignored: The following options were ignored:
module, target module, target
[WILDCARD]
error: TS1219 [ERROR]: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. error: TS1219 [ERROR]: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.
a() { a() {
^ ^

View file

@ -1,3 +1,4 @@
error: Modules imported via https are not allowed to import http modules. error: Modules imported via https are not allowed to import http modules.
Importing: http://localhost:4545/001_hello.js Importing: http://localhost:4545/001_hello.js
at https://localhost:5545/disallow_http_from_https.js:2:0 at https://localhost:5545/disallow_http_from_https.js:2:8

View file

@ -1,3 +1,4 @@
error: Modules imported via https are not allowed to import http modules. error: Modules imported via https are not allowed to import http modules.
Importing: http://localhost:4545/001_hello.js Importing: http://localhost:4545/001_hello.js
at https://localhost:5545/disallow_http_from_https.ts:2:0 at https://localhost:5545/disallow_http_from_https.ts:2:8

View file

@ -1,5 +1,5 @@
error: Uncaught (in promise) TypeError: Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag error: Uncaught (in promise) TypeError: Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
at blob:null/[WILDCARD]:1:0 at blob:null/[WILDCARD]:1:8
await import(URL.createObjectURL(blob)); await import(URL.createObjectURL(blob));
^ ^
at async file:///[WILDCARD]/dynamic_import/permissions_blob_local.ts:6:1 at async file://[WILDCARD]/permissions_blob_local.ts:6:1

View file

@ -1,5 +1,5 @@
error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag
at blob:null/[WILDCARD]:1:0 at blob:null/[WILDCARD]:1:8
await import(URL.createObjectURL(blob)); await import(URL.createObjectURL(blob));
^ ^
at async file:///[WILDCARD]/dynamic_import/permissions_blob_remote.ts:4:1 at async file:///[WILDCARD]/dynamic_import/permissions_blob_remote.ts:4:1

View file

@ -1,5 +1,5 @@
error: Uncaught (in promise) TypeError: Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag error: Uncaught (in promise) TypeError: Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
at data:application/javascript;base64,[WILDCARD]:1:0 at data:application/javascript;base64,[WILDCARD]:1:8
await import(`data:application/javascript;base64,${btoa(code)}`); await import(`data:application/javascript;base64,${btoa(code)}`);
^ ^
at async file:///[WILDCARD]/dynamic_import/permissions_data_local.ts:5:1 at async file:///[WILDCARD]/dynamic_import/permissions_data_local.ts:5:1

View file

@ -1,5 +1,5 @@
error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:0 at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
await import(`data:application/javascript;base64,${btoa(code)}`); await import(`data:application/javascript;base64,${btoa(code)}`);
^ ^
at async file:///[WILDCARD]/dynamic_import/permissions_data_remote.ts:3:1 at async file:///[WILDCARD]/dynamic_import/permissions_data_remote.ts:3:1

View file

@ -1,5 +1,5 @@
error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag
at http://localhost:4545/dynamic_import/static_remote.ts:2:0 at http://localhost:4545/dynamic_import/static_remote.ts:2:8
await import( await import(
^ ^
at async file:///[WILDCARD]/dynamic_import/permissions_remote_remote.ts:1:1 at async file:///[WILDCARD]/dynamic_import/permissions_remote_remote.ts:1:1

View file

@ -1,2 +1,2 @@
[WILDCARD]error: Cannot resolve module "file:///[WILDCARD]/bad-module.ts" from "file:///[WILDCARD]/error_004_missing_module.ts". [WILDCARD]error: Cannot load module "file:///[WILDCARD]/bad-module.ts".
at file:///[WILDCARD]/error_004_missing_module.ts:1:0 at file:///[WILDCARD]/error_004_missing_module.ts:1:28

View file

@ -1,4 +1,4 @@
error: Uncaught (in promise) TypeError: Cannot resolve module "[WILDCARD]/bad-module.ts". error: Uncaught (in promise) TypeError: Cannot load module "[WILDCARD]/bad-module.ts".
const _badModule = await import("./bad-module.ts"); const _badModule = await import("./bad-module.ts");
^ ^
at async file://[WILDCARD]/error_005_missing_dynamic_import.ts:2:22 at async file://[WILDCARD]/error_005_missing_dynamic_import.ts:2:22

View file

@ -1,2 +1,2 @@
[WILDCARD]error: Cannot resolve module "[WILDCARD]/non-existent" from "[WILDCARD]/error_006_import_ext_failure.ts". [WILDCARD]error: Cannot load module "[WILDCARD]/non-existent".
at file:///[WILDCARD]/error_006_import_ext_failure.ts:1:0 at file:///[WILDCARD]/error_006_import_ext_failure.ts:1:8

View file

@ -1 +1,3 @@
[WILDCARD]error: Relative import path "bad-module.ts" not prefixed with / or ./ or ../ from "[WILDCARD]/error_011_bad_module_specifier.ts" [WILDCARD]error: Relative import path "bad-module.ts" not prefixed with / or ./ or ../ from "[WILDCARD]/error_011_bad_module_specifier.ts"
at [WILDCARD]/error_011_bad_module_specifier.ts:1:28

View file

@ -1,5 +1,7 @@
Check [WILDCARD]error_012_bad_dynamic_import_specifier.ts Check [WILDCARD]error_012_bad_dynamic_import_specifier.ts
error: Uncaught (in promise) TypeError: Relative import path "bad-module.ts" not prefixed with / or ./ or ../ from "[WILDCARD]/error_012_bad_dynamic_import_specifier.ts" error: Uncaught (in promise) TypeError: Relative import path "bad-module.ts" not prefixed with / or ./ or ../ from "[WILDCARD]/error_012_bad_dynamic_import_specifier.ts"
at [WILDCARD]/error_012_bad_dynamic_import_specifier.ts:2:35
const _badModule = await import("bad-module.ts"); const _badModule = await import("bad-module.ts");
^ ^
at async file:///[WILDCARD]/error_012_bad_dynamic_import_specifier.ts:2:22 at async [WILDCARD]/error_012_bad_dynamic_import_specifier.ts:2:22

View file

@ -1 +1 @@
error: Cannot resolve module "[WILDCARD]missing_file_name". error: Cannot load module "[WILDCARD]missing_file_name".

View file

@ -1,12 +1,16 @@
Caught direct dynamic import error. Caught direct dynamic import error.
TypeError: Relative import path "does not exist" not prefixed with / or ./ or ../ from "[WILDCARD]/error_014_catch_dynamic_import_error.js" TypeError: Relative import path "does not exist" not prefixed with / or ./ or ../ from "[WILDCARD]/error_014_catch_dynamic_import_error.js"
at async file:///[WILDCARD]/error_014_catch_dynamic_import_error.js:3:5 at [WILDCARD]/error_014_catch_dynamic_import_error.js:3:18
at async [WILDCARD]/error_014_catch_dynamic_import_error.js:3:5
Caught indirect direct dynamic import error. Caught indirect direct dynamic import error.
TypeError: Relative import path "does not exist either" not prefixed with / or ./ or ../ from "[WILDCARD]/indirect_import_error.js" TypeError: Relative import path "does not exist either" not prefixed with / or ./ or ../ from "[WILDCARD]/subdir/indirect_import_error.js"
at async file:///[WILDCARD]/error_014_catch_dynamic_import_error.js:10:5 at [WILDCARD]/subdir/indirect_import_error.js:1:15
at async [WILDCARD]/error_014_catch_dynamic_import_error.js:10:5
Caught error thrown by dynamically imported module. Caught error thrown by dynamically imported module.
Error: An error Error: An error
at file:///[WILDCARD]/subdir/throws.js:6:7 at [WILDCARD]/subdir/throws.js:6:7
Caught error thrown indirectly by dynamically imported module. Caught error thrown indirectly by dynamically imported module.
Error: An error Error: An error
at file:///[WILDCARD]/subdir/throws.js:6:7 at [WILDCARD]/subdir/throws.js:6:7

View file

@ -1,4 +1,5 @@
error: Uncaught (in promise) TypeError: Requires net access to "localhost:4545", run again with the --allow-net flag error: Uncaught (in promise) TypeError: Requires net access to "localhost:4545", run again with the --allow-net flag
at file://[WILDCARD]/error_015_dynamic_import_permissions.js:2:16
await import("http://localhost:4545/subdir/mod4.js"); await import("http://localhost:4545/subdir/mod4.js");
^ ^
at async file:///[WILDCARD]/error_015_dynamic_import_permissions.js:2:3 at async file://[WILDCARD]/error_015_dynamic_import_permissions.js:2:3

View file

@ -1,4 +1,8 @@
[WILDCARD] [WILDCARD]
error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. error: Uncaught (in promise) TypeError: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
Importing: file:///c:/etc/passwd Importing: file:///c:/etc/passwd
at http://localhost:4545/subdir/evil_remote_import.js:3:0 at http://localhost:4545/subdir/evil_remote_import.js:3:15
await import("http://localhost:4545/subdir/evil_remote_import.js");
^
at async file://[WILDCARD]/error_016_dynamic_import_permissions2.js:4:3

View file

@ -1,4 +1,5 @@
[WILDCARD] [WILDCARD]
error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
Importing: file:///some/dir/file.js Importing: file:///some/dir/file.js
at http://localhost:4545/error_local_static_import_from_remote.js:1:0 at http://localhost:4545/error_local_static_import_from_remote.js:1:8

View file

@ -1,4 +1,5 @@
[WILDCARD] [WILDCARD]
error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
Importing: file:///some/dir/file.ts Importing: file:///some/dir/file.ts
at http://localhost:4545/error_local_static_import_from_remote.ts:1:0 at http://localhost:4545/error_local_static_import_from_remote.ts:1:8

View file

@ -1,2 +1,3 @@
error: Cannot resolve module "[WILDCARD]/does_not_exist.js" from "[WILDCARD]/error_missing_module_named_import.ts". [WILDCARD]
at [WILDCARD]/error_missing_module_named_import.ts:1:0 error: Cannot load module "file://[WILDCARD]/does_not_exist.js".
at file://[WILDCARD]/error_missing_module_named_import.ts:1:19

View file

@ -1 +1 @@
error: Expected ,, got following at [WILDCARD]/error_syntax.js:3:6 error: The module's source code could not be parsed: Expected ',', got 'following' at [WILDCARD]/error_syntax.js:3:6

View file

@ -1 +1 @@
error: Unexpected eof at [WILDCARD]/error_syntax_empty_trailing_line.mjs:2:22 error: The module's source code could not be parsed: Unexpected eof at [WILDCARD]/error_syntax_empty_trailing_line.mjs:2:22

View file

@ -1,4 +1,6 @@
error: Uncaught (in promise) TypeError: invalid URL: relative URL with a cannot-be-a-base base error: Uncaught (in promise) TypeError: invalid URL: relative URL with a cannot-be-a-base base
at blob:null/[WILDCARD]:1:19
const a = await import(url); const a = await import(url);
^ ^
at async file://[WILDCARD]/import_blob_url_import_relative.ts:6:11 at async file://[WILDCARD]/import_blob_url_import_relative.ts:6:11

View file

@ -1,4 +1,3 @@
error: invalid URL: relative URL with a cannot-be-a-base base error: invalid URL: relative URL with a cannot-be-a-base base
at data:application/javascript;base64,ZXhwb3J0IHsgYSB9IGZyb20gIi4vYS50cyI7Cg==:1:19
Caused by:
relative URL with a cannot-be-a-base base

View file

@ -1,6 +1,6 @@
local: [WILDCARD]error_009_missing_js_module.js local: [WILDCARD]error_009_missing_js_module.js
type: JavaScript type: JavaScript
dependencies: 1 unique (total 26B) dependencies: 0 unique (total 26B)
file://[WILDCARD]/error_009_missing_js_module.js (26B) file://[WILDCARD]/error_009_missing_js_module.js (26B)
└── file://[WILDCARD]/bad-module.js (error) └── file://[WILDCARD]/bad-module.js (missing)

View file

@ -58,7 +58,7 @@
"errors": [ "errors": [
{ {
"file_path": "[WILDCARD]malformed.js", "file_path": "[WILDCARD]malformed.js",
"message": "Expected }, got <eof> at [WILDCARD]malformed.js:4:16" "message": "Expected '}', got '<eof>' at [WILDCARD]malformed.js:4:16"
} }
] ]
} }

View file

@ -1,3 +1,3 @@
DANGER: TLS certificate validation is disabled for: deno.land DANGER: TLS certificate validation is disabled for: deno.land
error: error sending request for url (https://localhost:5545/subdir/mod2.ts): error trying to connect: invalid certificate: UnknownIssuer error: error sending request for url (https://localhost:5545/subdir/mod2.ts): error trying to connect: invalid certificate: UnknownIssuer
at [WILDCARD]/cafile_url_imports.ts:1:0 at file://[WILDCARD]/cafile_url_imports.ts:1:28

View file

@ -1,4 +1,4 @@
[WILDCARD] [WILDCARD]
The source code is invalid, as it does not match the expected hash in the lock file. error: The source code is invalid, as it does not match the expected hash in the lock file.
Specifier: http://127.0.0.1:4545/subdir/subdir2/mod2.ts Specifier: http://127.0.0.1:4545/subdir/subdir2/mod2.ts
Lock file: lock_check_err_with_bundle.json Lock file: lock_check_err_with_bundle.json

View file

@ -1,4 +1,4 @@
[WILDCARD] [WILDCARD]
The source code is invalid, as it does not match the expected hash in the lock file. error: The source code is invalid, as it does not match the expected hash in the lock file.
Specifier: http://127.0.0.1:4545/subdir/subdir2/mod2.ts Specifier: http://127.0.0.1:4545/subdir/subdir2/mod2.ts
Lock file: lock_dynamic_imports.json Lock file: lock_dynamic_imports.json

View file

@ -1 +1 @@
error: Unexpected token `}`. Expected an identifier, void, yield, null, await, break, a string literal, a numeric literal, true, false, `, -, import, this, typeof, {, [, ( at [WILDCARD]syntax_error.ts:4:1 error: The module's source code could not be parsed: Unexpected token `}`. Expected an identifier, void, yield, null, await, break, a string literal, a numeric literal, true, false, `, -, import, this, typeof, {, [, ( at [WILDCARD]syntax_error.ts:4:1

View file

@ -1,5 +1,5 @@
[WILDCARD] [WILDCARD]
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
[WILDCARD] [WILDCARD]
new SomeClass().test(); new SomeClass[WILDCARD].test();
[WILDCARD] [WILDCARD]

View file

@ -1,4 +1,4 @@
Check [WILDCARD]ts_type_only_import.ts Check file://[WILDCARD]/ts_type_only_import.ts
warning: Compiled module not found "[WILDCARD]ts_type_only_import.d.ts" warning: Cannot load module "file://[WILDCARD]/ts_type_only_import.d.ts".
From: [WILDCARD]ts_type_only_import.ts at file://[WILDCARD]/ts_type_only_import.ts:1:15
If the source module contains only types, use `import type` and `export type` to import it instead. If the source module contains only types, use `import type` and `export type` to import it instead.

View file

@ -1,3 +1,3 @@
[WILDCARD]error: Uncaught (in worker "") Cannot resolve module "file:///[WILDCARD]/workers/doesnt_exist.js". [WILDCARD]error: Uncaught (in worker "") Cannot load module "file:///[WILDCARD]/workers/doesnt_exist.js".
error: Uncaught (in promise) Error: Unhandled error event in child worker. error: Uncaught (in promise) Error: Unhandled error event in child worker.
at Worker.#pollControl ([WILDCARD]) at Worker.#pollControl ([WILDCARD])

View file

@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
at blob:null/[WILDCARD]:1:0 at blob:null/[WILDCARD]:1:8
error: Uncaught (in promise) Error: Unhandled error event in child worker. error: Uncaught (in promise) Error: Unhandled error event in child worker.
at Worker.#pollControl ([WILDCARD]) at Worker.#pollControl ([WILDCARD])

View file

@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
at blob:null/[WILDCARD]:1:0 at blob:null/[WILDCARD]:1:8
error: Uncaught (in promise) Error: Unhandled error event in child worker. error: Uncaught (in promise) Error: Unhandled error event in child worker.
at Worker.#pollControl ([WILDCARD]) at Worker.#pollControl ([WILDCARD])

View file

@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
at data:application/javascript;base64,[WILDCARD]:1:0 at data:application/javascript;base64,[WILDCARD]:1:8
error: Uncaught (in promise) Error: Unhandled error event in child worker. error: Uncaught (in promise) Error: Unhandled error event in child worker.
at Worker.#pollControl ([WILDCARD]) at Worker.#pollControl ([WILDCARD])

View file

@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:0 at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
error: Uncaught (in promise) Error: Unhandled error event in child worker. error: Uncaught (in promise) Error: Unhandled error event in child worker.
at Worker.#pollControl ([WILDCARD]) at Worker.#pollControl ([WILDCARD])

View file

@ -1,4 +1,5 @@
error: Uncaught (in worker "") (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in worker "") (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag
at http://localhost:4545/workers/dynamic_remote.ts:2:14
await import("https://example.com/some/file.ts"); await import("https://example.com/some/file.ts");
^ ^
at async http://localhost:4545/workers/dynamic_remote.ts:2:1 at async http://localhost:4545/workers/dynamic_remote.ts:2:1

View file

@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
at http://localhost:4545/workers/static_remote.ts:2:0 at http://localhost:4545/workers/static_remote.ts:2:8
error: Uncaught (in promise) Error: Unhandled error event in child worker. error: Uncaught (in promise) Error: Unhandled error event in child worker.
at Worker.#pollControl ([WILDCARD]) at Worker.#pollControl ([WILDCARD])

View file

@ -1,11 +1,12 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::colors; use crate::colors;
use crate::emit;
use crate::flags::Flags; use crate::flags::Flags;
use crate::fs_util::collect_files; use crate::fs_util::collect_files;
use crate::module_graph::TypeLib;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::source_maps::SourceMapGetter; use crate::source_maps::SourceMapGetter;
use deno_ast::swc::common::Span; use deno_ast::swc::common::Span;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::error::AnyError; use deno_core::error::AnyError;
@ -687,16 +688,15 @@ pub async fn cover_files(
let module_specifier = let module_specifier =
deno_core::resolve_url_or_path(&script_coverage.url)?; deno_core::resolve_url_or_path(&script_coverage.url)?;
ps.prepare_module_load( ps.prepare_module_load(
module_specifier.clone(), vec![module_specifier.clone()],
TypeLib::UnstableDenoWindow,
Permissions::allow_all(),
Permissions::allow_all(),
false, false,
ps.maybe_import_map.clone(), emit::TypeLib::UnstableDenoWindow,
Permissions::allow_all(),
Permissions::allow_all(),
) )
.await?; .await?;
let module_source = ps.load(module_specifier.clone(), None)?; let module_source = ps.load(module_specifier.clone(), None, false)?;
let script_source = &module_source.code; let script_source = &module_source.code;
let maybe_source_map = ps.get_source_map(&script_coverage.url); let maybe_source_map = ps.get_source_map(&script_coverage.url);

View file

@ -106,7 +106,9 @@ pub async fn print_docs(
let source_file_specifier = let source_file_specifier =
ModuleSpecifier::parse("deno://lib.deno.d.ts").unwrap(); ModuleSpecifier::parse("deno://lib.deno.d.ts").unwrap();
let graph = create_graph( let graph = create_graph(
source_file_specifier.clone(), vec![source_file_specifier.clone()],
false,
None,
&mut loader, &mut loader,
None, None,
None, None,
@ -142,7 +144,9 @@ pub async fn print_docs(
import_map: ps.maybe_import_map.clone(), import_map: ps.maybe_import_map.clone(),
}; };
let graph = create_graph( let graph = create_graph(
root_specifier.clone(), vec![root_specifier.clone()],
false,
None,
&mut loader, &mut loader,
Some(&resolver), Some(&resolver),
None, None,

View file

@ -1,8 +1,11 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ast::Location; use crate::ast::Location;
use crate::cache;
use crate::cache::CacherLoader;
use crate::colors; use crate::colors;
use crate::create_main_worker; use crate::create_main_worker;
use crate::emit;
use crate::file_fetcher::File; use crate::file_fetcher::File;
use crate::file_watcher; use crate::file_watcher;
use crate::file_watcher::ResolutionResult; use crate::file_watcher::ResolutionResult;
@ -11,15 +14,13 @@ use crate::fs_util::collect_specifiers;
use crate::fs_util::is_supported_test_ext; use crate::fs_util::is_supported_test_ext;
use crate::fs_util::is_supported_test_path; use crate::fs_util::is_supported_test_path;
use crate::located_script_name; use crate::located_script_name;
use crate::module_graph; use crate::lockfile;
use crate::module_graph::GraphBuilder;
use crate::module_graph::Module;
use crate::module_graph::TypeLib;
use crate::ops; use crate::ops;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
use crate::tokio_util; use crate::tokio_util;
use crate::tools::coverage::CoverageCollector; use crate::tools::coverage::CoverageCollector;
use crate::FetchHandler;
use deno_ast::swc::common::comments::CommentKind; use deno_ast::swc::common::comments::CommentKind;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::error::generic_error; use deno_core::error::generic_error;
@ -28,7 +29,6 @@ use deno_core::futures::future;
use deno_core::futures::stream; use deno_core::futures::stream;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt; use deno_core::futures::StreamExt;
use deno_core::parking_lot::Mutex;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::JsRuntime; use deno_core::JsRuntime;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
@ -500,7 +500,7 @@ async fn check_specifiers(
ps: ProcState, ps: ProcState,
permissions: Permissions, permissions: Permissions,
specifiers: Vec<(ModuleSpecifier, TestMode)>, specifiers: Vec<(ModuleSpecifier, TestMode)>,
lib: TypeLib, lib: emit::TypeLib,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let inline_files = fetch_inline_files( let inline_files = fetch_inline_files(
ps.clone(), ps.clone(),
@ -527,12 +527,12 @@ async fn check_specifiers(
ps.file_fetcher.insert_cached(file); ps.file_fetcher.insert_cached(file);
} }
ps.prepare_module_graph( ps.prepare_module_load(
specifiers, specifiers,
false,
lib.clone(), lib.clone(),
Permissions::allow_all(), Permissions::allow_all(),
permissions.clone(), permissions.clone(),
ps.maybe_import_map.clone(),
) )
.await?; .await?;
} }
@ -548,12 +548,12 @@ async fn check_specifiers(
}) })
.collect(); .collect();
ps.prepare_module_graph( ps.prepare_module_load(
module_specifiers, module_specifiers,
false,
lib, lib,
Permissions::allow_all(), Permissions::allow_all(),
permissions, permissions,
ps.maybe_import_map.clone(),
) )
.await?; .await?;
@ -743,11 +743,14 @@ fn collect_specifiers_with_test_mode(
Ok(specifiers_with_mode) Ok(specifiers_with_mode)
} }
/// Collects module and document specifiers with test modes via `collect_specifiers_with_test_mode` /// Collects module and document specifiers with test modes via
/// which are then pre-fetched and adjusted based on the media type. /// `collect_specifiers_with_test_mode` which are then pre-fetched and adjusted
/// based on the media type.
/// ///
/// Specifiers that do not have a known media type that can be executed as a module are marked as /// Specifiers that do not have a known media type that can be executed as a
/// `TestMode::Documentation`. /// module are marked as `TestMode::Documentation`. Type definition files
/// cannot be run, and therefore need to be marked as `TestMode::Documentation`
/// as well.
async fn fetch_specifiers_with_test_mode( async fn fetch_specifiers_with_test_mode(
ps: ProcState, ps: ProcState,
include: Vec<String>, include: Vec<String>,
@ -762,7 +765,9 @@ async fn fetch_specifiers_with_test_mode(
.fetch(specifier, &mut Permissions::allow_all()) .fetch(specifier, &mut Permissions::allow_all())
.await?; .await?;
if file.media_type == MediaType::Unknown { if file.media_type == MediaType::Unknown
|| file.media_type == MediaType::Dts
{
*mode = TestMode::Documentation *mode = TestMode::Documentation
} }
} }
@ -798,9 +803,9 @@ pub async fn run_tests(
} }
let lib = if flags.unstable { let lib = if flags.unstable {
TypeLib::UnstableDenoWindow emit::TypeLib::UnstableDenoWindow
} else { } else {
TypeLib::DenoWindow emit::TypeLib::DenoWindow
}; };
check_specifiers( check_specifiers(
@ -845,26 +850,33 @@ pub async fn run_tests_with_watch(
let permissions = Permissions::from_options(&flags.clone().into()); let permissions = Permissions::from_options(&flags.clone().into());
let lib = if flags.unstable { let lib = if flags.unstable {
TypeLib::UnstableDenoWindow emit::TypeLib::UnstableDenoWindow
} else { } else {
TypeLib::DenoWindow emit::TypeLib::DenoWindow
}; };
let handler = Arc::new(Mutex::new(FetchHandler::new(
&ps,
Permissions::allow_all(),
Permissions::allow_all(),
)?));
let include = include.unwrap_or_else(|| vec![".".to_string()]); let include = include.unwrap_or_else(|| vec![".".to_string()]);
let paths_to_watch: Vec<_> = include.iter().map(PathBuf::from).collect(); let paths_to_watch: Vec<_> = include.iter().map(PathBuf::from).collect();
let resolver = |changed: Option<Vec<PathBuf>>| { let resolver = |changed: Option<Vec<PathBuf>>| {
let mut cache = cache::FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
Permissions::allow_all(),
Permissions::allow_all(),
);
let paths_to_watch = paths_to_watch.clone(); let paths_to_watch = paths_to_watch.clone();
let paths_to_watch_clone = paths_to_watch.clone(); let paths_to_watch_clone = paths_to_watch.clone();
let handler = handler.clone(); let maybe_resolver =
let ps = ps.clone(); ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_imports = ps
.maybe_config_file
.as_ref()
.map(|cf| cf.to_maybe_imports())
.flatten();
let files_changed = changed.is_some(); let files_changed = changed.is_some();
let include = include.clone(); let include = include.clone();
let ignore = ignore.clone(); let ignore = ignore.clone();
@ -886,61 +898,51 @@ pub async fn run_tests_with_watch(
.collect() .collect()
}; };
let mut builder = GraphBuilder::new( let graph = deno_graph::create_graph(
handler, test_modules.clone(),
ps.maybe_import_map.clone(), false,
ps.lockfile.clone(), maybe_imports,
); cache.as_mut_loader(),
for specifier in test_modules.iter() { maybe_resolver.as_ref().map(|r| r.as_resolver()),
builder.add(specifier, false).await?; maybe_locker,
} None,
builder.analyze_config_file(&ps.maybe_config_file).await?; )
let graph = builder.get_graph(); .await;
graph.valid()?;
// TODO(@kitsonk) - This should be totally derivable from the graph.
for specifier in test_modules { for specifier in test_modules {
fn get_dependencies<'a>( fn get_dependencies<'a>(
graph: &'a module_graph::Graph, graph: &'a deno_graph::ModuleGraph,
module: &'a Module, maybe_module: Option<&'a deno_graph::Module>,
// This needs to be accessible to skip getting dependencies if they're already there, // This needs to be accessible to skip getting dependencies if they're already there,
// otherwise this will cause a stack overflow with circular dependencies // otherwise this will cause a stack overflow with circular dependencies
output: &mut HashSet<&'a ModuleSpecifier>, output: &mut HashSet<&'a ModuleSpecifier>,
) -> Result<(), AnyError> { ) {
for dep in module.dependencies.values() { if let Some(module) = maybe_module {
if let Some(specifier) = &dep.maybe_code { for dep in module.dependencies.values() {
if !output.contains(specifier) { if let Some(specifier) = &dep.get_code() {
output.insert(specifier); if !output.contains(specifier) {
output.insert(specifier);
get_dependencies( get_dependencies(graph, graph.get(specifier), output);
graph, }
graph.get_specifier(specifier)?,
output,
)?;
} }
} if let Some(specifier) = &dep.get_type() {
if let Some(specifier) = &dep.maybe_type { if !output.contains(specifier) {
if !output.contains(specifier) { output.insert(specifier);
output.insert(specifier);
get_dependencies( get_dependencies(graph, graph.get(specifier), output);
graph, }
graph.get_specifier(specifier)?,
output,
)?;
} }
} }
} }
Ok(())
} }
// This test module and all it's dependencies // This test module and all it's dependencies
let mut modules = HashSet::new(); let mut modules = HashSet::new();
modules.insert(&specifier); modules.insert(&specifier);
get_dependencies( get_dependencies(&graph, graph.get(&specifier), &mut modules);
&graph,
graph.get_specifier(&specifier)?,
&mut modules,
)?;
paths_to_watch.extend( paths_to_watch.extend(
modules modules

View file

@ -2,17 +2,14 @@
use crate::config_file::TsConfig; use crate::config_file::TsConfig;
use crate::diagnostics::Diagnostics; use crate::diagnostics::Diagnostics;
use crate::module_graph::Graph; use crate::emit;
use crate::module_graph::Stats;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::error::anyhow; use deno_core::error::anyhow;
use deno_core::error::bail;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::error::Context; use deno_core::error::Context;
use deno_core::located_script_name; use deno_core::located_script_name;
use deno_core::op_sync; use deno_core::op_sync;
use deno_core::parking_lot::Mutex;
use deno_core::resolve_url_or_path; use deno_core::resolve_url_or_path;
use deno_core::serde::de; use deno_core::serde::de;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
@ -25,6 +22,7 @@ use deno_core::ModuleSpecifier;
use deno_core::OpFn; use deno_core::OpFn;
use deno_core::RuntimeOptions; use deno_core::RuntimeOptions;
use deno_core::Snapshot; use deno_core::Snapshot;
use deno_graph::ModuleGraph;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -93,7 +91,7 @@ pub fn get_asset(asset: &str) -> Option<&'static str> {
} }
fn get_maybe_hash( fn get_maybe_hash(
maybe_source: &Option<String>, maybe_source: Option<&String>,
hash_data: &[Vec<u8>], hash_data: &[Vec<u8>],
) -> Option<String> { ) -> Option<String> {
if let Some(source) = maybe_source { if let Some(source) = maybe_source {
@ -105,30 +103,15 @@ fn get_maybe_hash(
} }
} }
fn hash_data_url( /// Hash the URL so it can be sent to `tsc` in a supportable way
specifier: &ModuleSpecifier, fn hash_url(specifier: &ModuleSpecifier, media_type: &MediaType) -> String {
media_type: &MediaType,
) -> String {
assert_eq!(
specifier.scheme(),
"data",
"Specifier must be a data: specifier."
);
let hash = crate::checksum::gen(&[specifier.path().as_bytes()]); let hash = crate::checksum::gen(&[specifier.path().as_bytes()]);
format!("data:///{}{}", hash, media_type.as_ts_extension()) format!(
} "{}:///{}{}",
fn hash_blob_url(
specifier: &ModuleSpecifier,
media_type: &MediaType,
) -> String {
assert_eq!(
specifier.scheme(), specifier.scheme(),
"blob", hash,
"Specifier must be a blob: specifier." media_type.as_ts_extension()
); )
let hash = crate::checksum::gen(&[specifier.path().as_bytes()]);
format!("blob:///{}{}", hash, media_type.as_ts_extension())
} }
/// tsc only supports `.ts`, `.tsx`, `.d.ts`, `.js`, or `.jsx` as root modules /// tsc only supports `.ts`, `.tsx`, `.d.ts`, `.js`, or `.jsx` as root modules
@ -180,7 +163,7 @@ pub struct Request {
pub config: TsConfig, pub config: TsConfig,
/// Indicates to the tsc runtime if debug logging should occur. /// Indicates to the tsc runtime if debug logging should occur.
pub debug: bool, pub debug: bool,
pub graph: Arc<Mutex<Graph>>, pub graph: Arc<ModuleGraph>,
pub hash_data: Vec<Vec<u8>>, pub hash_data: Vec<Vec<u8>>,
pub maybe_config_specifier: Option<ModuleSpecifier>, pub maybe_config_specifier: Option<ModuleSpecifier>,
pub maybe_tsbuildinfo: Option<String>, pub maybe_tsbuildinfo: Option<String>,
@ -190,7 +173,7 @@ pub struct Request {
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct Response { pub(crate) struct Response {
/// Any diagnostics that have been returned from the checker. /// Any diagnostics that have been returned from the checker.
pub diagnostics: Diagnostics, pub diagnostics: Diagnostics,
/// Any files that were emitted during the check. /// Any files that were emitted during the check.
@ -198,7 +181,7 @@ pub struct Response {
/// If there was any build info associated with the exec request. /// If there was any build info associated with the exec request.
pub maybe_tsbuildinfo: Option<String>, pub maybe_tsbuildinfo: Option<String>,
/// Statistics from the check. /// Statistics from the check.
pub stats: Stats, pub stats: emit::Stats,
} }
#[derive(Debug)] #[derive(Debug)]
@ -206,7 +189,7 @@ struct State {
data_url_map: HashMap<String, ModuleSpecifier>, data_url_map: HashMap<String, ModuleSpecifier>,
hash_data: Vec<Vec<u8>>, hash_data: Vec<Vec<u8>>,
emitted_files: Vec<EmittedFile>, emitted_files: Vec<EmittedFile>,
graph: Arc<Mutex<Graph>>, graph: Arc<ModuleGraph>,
maybe_config_specifier: Option<ModuleSpecifier>, maybe_config_specifier: Option<ModuleSpecifier>,
maybe_tsbuildinfo: Option<String>, maybe_tsbuildinfo: Option<String>,
maybe_response: Option<RespondArgs>, maybe_response: Option<RespondArgs>,
@ -215,7 +198,7 @@ struct State {
impl State { impl State {
pub fn new( pub fn new(
graph: Arc<Mutex<Graph>>, graph: Arc<ModuleGraph>,
hash_data: Vec<Vec<u8>>, hash_data: Vec<Vec<u8>>,
maybe_config_specifier: Option<ModuleSpecifier>, maybe_config_specifier: Option<ModuleSpecifier>,
maybe_tsbuildinfo: Option<String>, maybe_tsbuildinfo: Option<String>,
@ -334,8 +317,7 @@ fn op_exists(state: &mut State, args: ExistsArgs) -> Result<bool, AnyError> {
if specifier.scheme() == "asset" || specifier.scheme() == "data" { if specifier.scheme() == "asset" || specifier.scheme() == "data" {
Ok(true) Ok(true)
} else { } else {
let graph = state.graph.lock(); Ok(state.graph.contains(&specifier))
Ok(graph.contains(&specifier))
} }
} else { } else {
Ok(false) Ok(false)
@ -366,11 +348,10 @@ fn op_load(state: &mut State, args: Value) -> Result<Value, AnyError> {
} else if v.specifier.starts_with("asset:///") { } else if v.specifier.starts_with("asset:///") {
let name = v.specifier.replace("asset:///", ""); let name = v.specifier.replace("asset:///", "");
let maybe_source = get_asset(&name).map(String::from); let maybe_source = get_asset(&name).map(String::from);
hash = get_maybe_hash(&maybe_source, &state.hash_data); hash = get_maybe_hash(maybe_source.as_ref(), &state.hash_data);
media_type = MediaType::from(&v.specifier); media_type = MediaType::from(&v.specifier);
maybe_source maybe_source
} else { } else {
let graph = state.graph.lock();
let specifier = if let Some(data_specifier) = let specifier = if let Some(data_specifier) =
state.data_url_map.get(&v.specifier) state.data_url_map.get(&v.specifier)
{ {
@ -380,13 +361,14 @@ fn op_load(state: &mut State, args: Value) -> Result<Value, AnyError> {
} else { } else {
specifier specifier
}; };
let maybe_source = graph.get_source(&specifier).map(|t| t.to_string()); let maybe_source = if let Some(module) = state.graph.get(&specifier) {
media_type = if let Some(media_type) = graph.get_media_type(&specifier) { media_type = module.media_type;
media_type Some(module.source.as_str().to_string())
} else { } else {
MediaType::Unknown media_type = MediaType::Unknown;
None
}; };
hash = get_maybe_hash(&maybe_source, &state.hash_data); hash = get_maybe_hash(maybe_source.as_ref(), &state.hash_data);
maybe_source maybe_source
}; };
@ -425,48 +407,31 @@ fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
MediaType::from(specifier).as_ts_extension().to_string(), MediaType::from(specifier).as_ts_extension().to_string(),
)); ));
} else { } else {
let graph = state.graph.lock(); let resolved_dependency =
match graph.resolve(specifier, &referrer, true) { match state.graph.resolve_dependency(specifier, &referrer, true) {
Ok(resolved_specifier) => { Some(resolved_specifier) => {
let media_type = if let Some(media_type) = let media_type = state
graph.get_media_type(&resolved_specifier) .graph
{ .get(resolved_specifier)
media_type .map_or(&MediaType::Unknown, |m| &m.media_type);
} else { let resolved_specifier_str = match resolved_specifier.scheme() {
bail!( "data" | "blob" => {
"Unable to resolve media type for specifier: \"{}\"", let specifier_str = hash_url(resolved_specifier, media_type);
resolved_specifier state
) .data_url_map
}; .insert(specifier_str.clone(), resolved_specifier.clone());
let resolved_specifier_str = match resolved_specifier.scheme() { specifier_str
"data" | "blob" => { }
let specifier_str = if resolved_specifier.scheme() == "data" { _ => resolved_specifier.to_string(),
hash_data_url(&resolved_specifier, &media_type) };
} else { (resolved_specifier_str, media_type.as_ts_extension().into())
hash_blob_url(&resolved_specifier, &media_type) }
}; None => (
state
.data_url_map
.insert(specifier_str.clone(), resolved_specifier);
specifier_str
}
_ => resolved_specifier.to_string(),
};
resolved.push((
resolved_specifier_str,
media_type.as_ts_extension().into(),
));
}
// in certain situations, like certain dynamic imports, we won't have
// the source file in the graph, so we will return a fake module to
// make tsc happy.
Err(_) => {
resolved.push((
"deno:///missing_dependency.d.ts".to_string(), "deno:///missing_dependency.d.ts".to_string(),
".d.ts".to_string(), ".d.ts".to_string(),
)); ),
} };
} resolved.push(resolved_dependency);
} }
} }
@ -476,7 +441,7 @@ fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
#[derive(Debug, Deserialize, Eq, PartialEq)] #[derive(Debug, Deserialize, Eq, PartialEq)]
struct RespondArgs { struct RespondArgs {
pub diagnostics: Diagnostics, pub diagnostics: Diagnostics,
pub stats: Stats, pub stats: emit::Stats,
} }
fn op_respond(state: &mut State, args: Value) -> Result<Value, AnyError> { fn op_respond(state: &mut State, args: Value) -> Result<Value, AnyError> {
@ -489,7 +454,7 @@ fn op_respond(state: &mut State, args: Value) -> Result<Value, AnyError> {
/// Execute a request on the supplied snapshot, returning a response which /// Execute a request on the supplied snapshot, returning a response which
/// contains information, like any emitted files, diagnostics, statistics and /// contains information, like any emitted files, diagnostics, statistics and
/// optionally an updated TypeScript build info. /// optionally an updated TypeScript build info.
pub fn exec(request: Request) -> Result<Response, AnyError> { pub(crate) fn exec(request: Request) -> Result<Response, AnyError> {
let mut runtime = JsRuntime::new(RuntimeOptions { let mut runtime = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(compiler_snapshot()), startup_snapshot: Some(compiler_snapshot()),
..Default::default() ..Default::default()
@ -505,11 +470,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
.iter() .iter()
.map(|(s, mt)| match s.scheme() { .map(|(s, mt)| match s.scheme() {
"data" | "blob" => { "data" | "blob" => {
let specifier_str = if s.scheme() == "data" { let specifier_str = hash_url(s, mt);
hash_data_url(s, mt)
} else {
hash_blob_url(s, mt)
};
data_url_map.insert(specifier_str.clone(), s.clone()); data_url_map.insert(specifier_str.clone(), s.clone());
specifier_str specifier_str
} }
@ -530,7 +491,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
let op_state = runtime.op_state(); let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut(); let mut op_state = op_state.borrow_mut();
op_state.put(State::new( op_state.put(State::new(
request.graph.clone(), request.graph,
request.hash_data.clone(), request.hash_data.clone(),
request.maybe_config_specifier.clone(), request.maybe_config_specifier.clone(),
request.maybe_tsbuildinfo.clone(), request.maybe_tsbuildinfo.clone(),
@ -589,9 +550,39 @@ mod tests {
use crate::config_file::TsConfig; use crate::config_file::TsConfig;
use crate::diagnostics::Diagnostic; use crate::diagnostics::Diagnostic;
use crate::diagnostics::DiagnosticCategory; use crate::diagnostics::DiagnosticCategory;
use crate::module_graph::tests::MockSpecifierHandler; use crate::emit::Stats;
use crate::module_graph::GraphBuilder; use deno_core::futures::future;
use deno_core::parking_lot::Mutex; use std::fs;
#[derive(Debug, Default)]
pub(crate) struct MockLoader {
pub fixtures: PathBuf,
}
impl deno_graph::source::Loader for MockLoader {
fn load(
&mut self,
specifier: &ModuleSpecifier,
_is_dynamic: bool,
) -> deno_graph::source::LoadFuture {
let specifier_text = specifier
.to_string()
.replace(":///", "_")
.replace("://", "_")
.replace("/", "-");
let source_path = self.fixtures.join(specifier_text);
let response = fs::read_to_string(&source_path)
.map(|c| {
Some(deno_graph::source::LoadResponse {
specifier: specifier.clone(),
maybe_headers: None,
content: Arc::new(c),
})
})
.map_err(|err| err.into());
Box::pin(future::ready((specifier.clone(), response)))
}
}
async fn setup( async fn setup(
maybe_specifier: Option<ModuleSpecifier>, maybe_specifier: Option<ModuleSpecifier>,
@ -602,16 +593,19 @@ mod tests {
.unwrap_or_else(|| resolve_url_or_path("file:///main.ts").unwrap()); .unwrap_or_else(|| resolve_url_or_path("file:///main.ts").unwrap());
let hash_data = maybe_hash_data.unwrap_or_else(|| vec![b"".to_vec()]); let hash_data = maybe_hash_data.unwrap_or_else(|| vec![b"".to_vec()]);
let fixtures = test_util::testdata_path().join("tsc2"); let fixtures = test_util::testdata_path().join("tsc2");
let handler = Arc::new(Mutex::new(MockSpecifierHandler { let mut loader = MockLoader { fixtures };
fixtures, let graph = Arc::new(
..MockSpecifierHandler::default() deno_graph::create_graph(
})); vec![specifier],
let mut builder = GraphBuilder::new(handler.clone(), None, None); false,
builder None,
.add(&specifier, false) &mut loader,
.await None,
.expect("module not inserted"); None,
let graph = Arc::new(Mutex::new(builder.get_graph())); None,
)
.await,
);
State::new( State::new(
graph, graph,
hash_data, hash_data,
@ -627,13 +621,19 @@ mod tests {
) -> Result<Response, AnyError> { ) -> Result<Response, AnyError> {
let hash_data = vec![b"something".to_vec()]; let hash_data = vec![b"something".to_vec()];
let fixtures = test_util::testdata_path().join("tsc2"); let fixtures = test_util::testdata_path().join("tsc2");
let handler = Arc::new(Mutex::new(MockSpecifierHandler { let mut loader = MockLoader { fixtures };
fixtures, let graph = Arc::new(
..Default::default() deno_graph::create_graph(
})); vec![specifier.clone()],
let mut builder = GraphBuilder::new(handler.clone(), None, None); false,
builder.add(specifier, false).await?; None,
let graph = Arc::new(Mutex::new(builder.get_graph())); &mut loader,
None,
None,
None,
)
.await,
);
let config = TsConfig::new(json!({ let config = TsConfig::new(json!({
"allowJs": true, "allowJs": true,
"checkJs": false, "checkJs": false,
@ -695,12 +695,12 @@ mod tests {
} }
#[test] #[test]
fn test_hash_data_url() { fn test_hash_url() {
let specifier = deno_core::resolve_url( let specifier = deno_core::resolve_url(
"data:application/javascript,console.log(\"Hello%20Deno\");", "data:application/javascript,console.log(\"Hello%20Deno\");",
) )
.unwrap(); .unwrap();
assert_eq!(hash_data_url(&specifier, &MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js"); assert_eq!(hash_url(&specifier, &MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js");
} }
#[test] #[test]