1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -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]
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_doc = "0.14.0"
deno_graph = "0.5.0"
deno_doc = "0.15.0"
deno_graph = "0.6.0"
deno_lint = { version = "0.16.0", features = ["docs"] }
deno_runtime = { version = "0.28.0", path = "../runtime" }
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_core::error::AnyError;
/// This contains the logic for Deno to rewrite the `import.meta` when bundling.
pub struct 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;
static STD_NODE: &str = "https://deno.land/std/node/";
static GLOBAL_MODULE: &str = "global.ts";
static SUPPORTED_MODULES: &[&str] = &[
"assert",
@ -50,8 +51,15 @@ static SUPPORTED_MODULES: &[&str] = &[
"zlib",
];
pub fn get_node_globals_url() -> Url {
Url::parse(&format!("{}global.ts", STD_NODE)).unwrap()
lazy_static::lazy_static! {
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.

View file

@ -10,6 +10,7 @@ use deno_core::serde::Serializer;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use std::collections::BTreeMap;
use std::collections::HashMap;
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> {
if let Some(config) = self.json.fmt.clone() {
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::Serialize;
use deno_core::serde::Serializer;
use deno_core::ModuleSpecifier;
use deno_graph::ModuleGraphError;
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
@ -353,20 +352,17 @@ impl Diagnostics {
Diagnostics(diagnostics)
}
pub fn extend_graph_errors(
&mut self,
errors: HashMap<ModuleSpecifier, String>,
) {
self.0.extend(errors.into_iter().map(|(s, e)| Diagnostic {
pub fn extend_graph_errors(&mut self, errors: Vec<ModuleGraphError>) {
self.0.extend(errors.into_iter().map(|err| Diagnostic {
category: DiagnosticCategory::Error,
code: 900001,
start: None,
end: None,
message_text: Some(e),
message_text: Some(err.to_string()),
message_chain: None,
source: None,
source_line: None,
file_name: Some(s.to_string()),
file_name: Some(err.specifier().to_string()),
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
//! exceptions.
use crate::emit::GraphError;
use deno_ast::Diagnostic;
use deno_core::error::AnyError;
use deno_graph::ModuleGraphError;
use deno_graph::ResolutionError;
use import_map::ImportMapError;
fn get_import_map_error_class(_: &ImportMapError) -> &'static str {
@ -21,6 +25,34 @@ fn get_diagnostic_class(_: &Diagnostic) -> &'static str {
"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 {
deno_runtime::errors::get_error_class_name(e)
.or_else(|| {
@ -28,6 +60,15 @@ pub(crate) fn get_error_class_name(e: &AnyError) -> &'static str {
.map(get_import_map_error_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(|| {
panic!(
"Error '{}' contains boxed error of unknown type:{}",

View file

@ -371,7 +371,9 @@ impl FileFetcher {
})?;
let mut headers = HashMap::new();
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 {
local,
@ -379,7 +381,7 @@ impl FileFetcher {
media_type,
source: Arc::new(source),
specifier: specifier.clone(),
maybe_headers: None,
maybe_headers: Some(headers),
})
}
@ -433,7 +435,9 @@ impl FileFetcher {
})?;
let mut headers = HashMap::new();
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 {
local,
@ -441,7 +445,7 @@ impl FileFetcher {
media_type,
source: Arc::new(source),
specifier: specifier.clone(),
maybe_headers: None,
maybe_headers: Some(headers),
})
}
/// 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.
pub fn get_http_cache_location(&self) -> PathBuf {
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.
use deno_core::parking_lot::Mutex;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::ModuleSpecifier;
use log::debug;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::io::Result;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug, Clone)]
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)]
mod tests {
use super::*;

View file

@ -6,12 +6,12 @@ use super::tsc;
use crate::ast;
use crate::ast::Location;
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 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::visit::Node;
use deno_ast::swc::visit::Visit;
@ -68,10 +68,71 @@ lazy_static::lazy_static! {
.collect();
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"];
// 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)
/// TypeScript.
#[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::CodeActionData;
use super::analysis::ResolvedDependency;
use super::cache::CacheServer;
use super::capabilities;
use super::code_lens;
use super::completions;
@ -44,7 +45,6 @@ use super::parent_process_checker;
use super::performance::Performance;
use super::refactor;
use super::registries;
use super::sources;
use super::sources::Sources;
use super::text;
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
/// options.
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
/// options.
maybe_config_file: Option<ConfigFile>,
@ -149,6 +151,7 @@ impl Inner {
diagnostics_server,
documents: Default::default(),
maybe_cache_path: None,
maybe_cache_server: None,
maybe_config_file: None,
maybe_config_uri: None,
maybe_import_map: None,
@ -424,6 +427,7 @@ impl Inner {
pub fn update_cache(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_cache", None::<()>);
self.performance.measure(mark);
self.maybe_cache_server = None;
let (maybe_cache, maybe_root_uri) = {
let config = &self.config;
(
@ -479,6 +483,7 @@ impl Inner {
pub async fn update_import_map(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_import_map", None::<()>);
self.maybe_cache_server = None;
let (maybe_import_map, maybe_root_uri) = {
let config = &self.config;
(
@ -2630,34 +2635,30 @@ impl Inner {
}
let mark = self.performance.mark("cache", Some(&params));
if !params.uris.is_empty() {
for identifier in &params.uris {
let specifier = self.url_map.normalize_url(&identifier.uri);
sources::cache(
&specifier,
&self.maybe_import_map,
&self.maybe_config_file,
&self.maybe_cache_path,
)
.await
.map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?;
}
let roots = if !params.uris.is_empty() {
params
.uris
.iter()
.map(|t| self.url_map.normalize_url(&t.uri))
.collect()
} else {
sources::cache(
&referrer,
&self.maybe_import_map,
&self.maybe_config_file,
&self.maybe_cache_path,
)
.await
.map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?;
vec![referrer.clone()]
};
if self.maybe_cache_server.is_none() {
self.maybe_cache_server = Some(
CacheServer::new(
self.maybe_cache_path.clone(),
self.maybe_import_map.clone(),
)
.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
// invalidate some diagnostics
if self.documents.contains_key(&referrer) {

View file

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

View file

@ -6,18 +6,12 @@ use super::text::LineIndex;
use super::tsc;
use super::urls::INVALID_SPECIFIER;
use crate::config_file::ConfigFile;
use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::map_content_type;
use crate::file_fetcher::SUPPORTED_SCHEMES;
use crate::flags::Flags;
use crate::http_cache;
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 import_map::ImportMap;
use deno_ast::MediaType;
use deno_core::error::anyhow;
@ -25,7 +19,7 @@ use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use deno_core::serde_json;
use deno_core::ModuleSpecifier;
use deno_runtime::permissions::Permissions;
use import_map::ImportMap;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
@ -34,27 +28,6 @@ use std::sync::Arc;
use std::time::SystemTime;
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(
cache_filename: &Path,
) -> Option<HashMap<String, String>> {

View file

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

View file

@ -2,6 +2,7 @@
mod ast;
mod auth_tokens;
mod cache;
mod checksum;
mod compat;
mod config_file;
@ -9,6 +10,7 @@ mod deno_dir;
mod diagnostics;
mod diff;
mod disk_cache;
mod emit;
mod errors;
mod file_fetcher;
mod file_watcher;
@ -18,16 +20,14 @@ mod fmt_errors;
mod fs_util;
mod http_cache;
mod http_util;
mod info;
mod lockfile;
mod logger;
mod lsp;
mod module_graph;
mod module_loader;
mod ops;
mod proc_state;
mod resolver;
mod source_maps;
mod specifier_handler;
mod standalone;
mod text_encoding;
mod tokio_util;
@ -59,8 +59,8 @@ use crate::flags::UpgradeFlags;
use crate::fmt_errors::PrettyJsError;
use crate::module_loader::CliModuleLoader;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
use crate::source_maps::apply_source_map;
use crate::specifier_handler::FetchHandler;
use crate::tools::installer::infer_name_from_url;
use deno_ast::MediaType;
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;
use deno_core::located_script_name;
use deno_core::parking_lot::Mutex;
use deno_core::resolve_url_or_path;
use deno_core::serde_json;
use deno_core::serde_json::json;
@ -299,7 +298,7 @@ where
fn print_cache_info(
state: &ProcState,
json: bool,
location: Option<deno_core::url::Url>,
location: Option<&deno_core::url::Url>,
) -> Result<(), AnyError> {
let deno_dir = &state.dir.root;
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.",
))?;
let module_graph = create_module_graph_and_maybe_check(
module_specifier.clone(),
ps.clone(),
debug,
)
.await?;
info!(
"{} {}",
colors::green("Bundle"),
module_specifier.to_string()
);
let bundle_str = bundle_module_graph(module_graph, ps.clone(), flags, debug)?;
let graph =
create_graph_and_maybe_check(module_specifier.clone(), &ps, debug).await?;
let (bundle_str, _) = bundle_module_graph(graph.as_ref(), &ps, &flags)?;
info!(
"{} {}",
@ -449,36 +438,38 @@ async fn info_command(
flags: Flags,
info_flags: InfoFlags,
) -> Result<(), AnyError> {
let location = flags.location.clone();
let ps = ProcState::build(flags).await?;
if let Some(specifier) = info_flags.file {
let specifier = resolve_url_or_path(&specifier)?;
let handler = Arc::new(Mutex::new(specifier_handler::FetchHandler::new(
&ps,
// info accesses dynamically imported modules just for their information
// so we allow access to all of them.
let mut cache = cache::FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
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?;
builder.analyze_config_file(&ps.maybe_config_file).await?;
let graph = builder.get_graph();
let info = graph.info()?;
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_resolver =
ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
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 {
write_json_to_stdout(&json!(info))
write_json_to_stdout(&json!(graph))
} 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())
}
} else {
// 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,
) -> Result<(), AnyError> {
let lib = if flags.unstable {
module_graph::TypeLib::UnstableDenoWindow
emit::TypeLib::UnstableDenoWindow
} else {
module_graph::TypeLib::DenoWindow
emit::TypeLib::DenoWindow
};
let ps = ProcState::build(flags).await?;
for file in cache_flags.files {
let specifier = resolve_url_or_path(&file)?;
ps.prepare_module_load(
specifier,
vec![specifier],
false,
lib.clone(),
Permissions::allow_all(),
Permissions::allow_all(),
false,
ps.maybe_import_map.clone(),
)
.await?;
if let Some(graph_error) = ps.maybe_graph_error.lock().take() {
return Err(graph_error.into());
}
}
Ok(())
@ -567,8 +560,10 @@ async fn eval_command(
flags: Flags,
eval_flags: EvalFlags,
) -> Result<(), AnyError> {
// Force TypeScript compile.
let main_module = resolve_url_or_path("./$deno$eval.ts").unwrap();
// deno_graph works off of extensions for local files to determine the media
// 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 ps = ProcState::build(flags.clone()).await?;
let mut worker =
@ -584,15 +579,7 @@ async fn eval_command(
let file = File {
local: main_module.clone().to_file_path().unwrap(),
maybe_types: None,
media_type: if eval_flags.ext.as_str() == "ts" {
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
},
media_type: MediaType::Unknown,
source: Arc::new(String::from_utf8(source_code)?),
specifier: main_module.clone(),
maybe_headers: None,
@ -603,9 +590,7 @@ async fn eval_command(
ps.file_fetcher.insert_cached(file);
debug!("main_module {}", &main_module);
if flags.compat {
worker
.execute_side_module(&compat::get_node_globals_url())
.await?;
worker.execute_side_module(&compat::GLOBAL_URL).await?;
}
worker.execute_main_module(&main_module).await?;
worker.execute_script(
@ -620,75 +605,133 @@ async fn eval_command(
Ok(())
}
async fn create_module_graph_and_maybe_check(
module_specifier: ModuleSpecifier,
ps: ProcState,
async fn create_graph_and_maybe_check(
root: ModuleSpecifier,
ps: &ProcState,
debug: bool,
) -> Result<module_graph::Graph, AnyError> {
let handler = Arc::new(Mutex::new(FetchHandler::new(
&ps,
// when bundling, dynamic imports are only access for their type safety,
// therefore we will allow the graph to access any module.
) -> Result<Arc<deno_graph::ModuleGraph>, AnyError> {
let mut cache = cache::FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
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?;
builder.analyze_config_file(&ps.maybe_config_file).await?;
let module_graph = builder.get_graph();
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 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 {
// TODO(@kitsonk) support bundling for workers
graph.valid_types_only().map_err(emit::GraphError::from)?;
let lib = if ps.flags.unstable {
module_graph::TypeLib::UnstableDenoWindow
emit::TypeLib::UnstableDenoWindow
} else {
module_graph::TypeLib::DenoWindow
emit::TypeLib::DenoWindow
};
let result_info =
module_graph.clone().check(module_graph::CheckOptions {
debug,
emit: false,
let (ts_config, maybe_ignored_options) = emit::get_ts_config(
emit::ConfigType::Check {
tsc_emit: false,
lib,
maybe_config_file: ps.maybe_config_file.clone(),
reload: ps.flags.reload,
..Default::default()
})?;
debug!("{}", result_info.stats);
if let Some(ignored_options) = result_info.maybe_ignored_options {
},
ps.maybe_config_file.as_ref(),
None,
)?;
log::info!("{} {}", colors::green("Check"), graph.roots[0]);
if let Some(ignored_options) = maybe_ignored_options {
eprintln!("{}", ignored_options);
}
if !result_info.diagnostics.is_empty() {
return Err(generic_error(result_info.diagnostics.to_string()));
let maybe_config_specifier = ps
.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(
module_graph: module_graph::Graph,
ps: ProcState,
flags: Flags,
debug: bool,
) -> Result<String, AnyError> {
let (bundle, stats, maybe_ignored_options) =
module_graph.bundle(module_graph::BundleOptions {
debug,
maybe_config_file: ps.maybe_config_file.clone(),
})?;
match maybe_ignored_options {
Some(ignored_options) if flags.no_check => {
graph: &deno_graph::ModuleGraph,
ps: &ProcState,
flags: &Flags,
) -> Result<(String, Option<String>), AnyError> {
info!("{} {}", colors::green("Bundle"), graph.roots[0]);
let (ts_config, maybe_ignored_options) = emit::get_ts_config(
emit::ConfigType::Bundle,
ps.maybe_config_file.as_ref(),
None,
)?;
if flags.no_check {
if let Some(ignored_options) = maybe_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(
@ -707,17 +750,18 @@ async fn bundle_command(
debug!(">>>>> bundle START");
let ps = ProcState::build(flags.clone()).await?;
let module_graph = create_module_graph_and_maybe_check(
module_specifier,
ps.clone(),
debug,
)
.await?;
let graph =
create_graph_and_maybe_check(module_specifier, &ps, debug).await?;
let mut paths_to_watch: Vec<PathBuf> = module_graph
.get_modules()
let mut paths_to_watch: Vec<PathBuf> = graph
.specifiers()
.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();
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))?);
}
Ok((paths_to_watch, module_graph, ps))
Ok((paths_to_watch, graph, ps))
}
.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,
result: Ok((ps, module_graph)),
result: Ok((ps, graph)),
},
Err(e) => ResolutionResult::Restart {
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 out_file = bundle_flags.out_file.clone();
async move {
info!("{} {}", colors::green("Bundle"), module_graph.info()?.root);
let output = bundle_module_graph(module_graph, ps, flags, debug)?;
let (bundle_emit, maybe_bundle_map) =
bundle_module_graph(graph.as_ref(), &ps, &flags)?;
debug!(">>>>> bundle END");
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();
fs_util::write_file(out_file, output_bytes, 0o644)?;
info!(
"{} {:?} ({})",
colors::green("Emit"),
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 {
println!("{}", output);
println!("{}", bundle_emit);
}
Ok(())
@ -825,9 +884,7 @@ async fn run_repl(flags: Flags, repl_flags: ReplFlags) -> Result<(), AnyError> {
let mut worker =
create_main_worker(&ps, main_module.clone(), permissions, None);
if flags.compat {
worker
.execute_side_module(&compat::get_node_globals_url())
.await?;
worker.execute_side_module(&compat::GLOBAL_URL).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);
if flags.compat {
worker
.execute_side_module(&compat::get_node_globals_url())
.await?;
worker.execute_side_module(&compat::GLOBAL_URL).await?;
}
worker.execute_main_module(&main_module).await?;
worker.execute_script(
@ -883,25 +938,42 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
async move {
let main_module = resolve_url_or_path(&script1)?;
let ps = ProcState::build(flags).await?;
let handler = Arc::new(Mutex::new(FetchHandler::new(
&ps,
let mut cache = cache::FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
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?;
builder.analyze_config_file(&ps.maybe_config_file).await?;
let module_graph = builder.get_graph();
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 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
let mut paths_to_watch: Vec<PathBuf> = module_graph
.get_modules()
let mut paths_to_watch: Vec<PathBuf> = graph
.specifiers()
.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();
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,
) -> Result<(), AnyError> {
if self.compat {
self
.worker
.execute_side_module(&compat::get_node_globals_url())
.await?;
self.worker.execute_side_module(&compat::GLOBAL_URL).await?;
}
self.worker.execute_main_module(main_module).await?;
self.worker.execute_script(
@ -1046,9 +1115,7 @@ async fn run_command(
debug!("main_module {}", main_module);
if flags.compat {
worker
.execute_side_module(&compat::get_node_globals_url())
.await?;
worker.execute_side_module(&compat::GLOBAL_URL).await?;
}
worker.execute_main_module(&main_module).await?;
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.
use crate::module_graph::TypeLib;
use crate::emit::TypeLib;
use crate::proc_state::ProcState;
use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt;
use deno_core::futures::Future;
@ -10,16 +11,12 @@ use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_runtime::permissions::Permissions;
use import_map::ImportMap;
use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use std::str;
pub 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(crate) struct CliModuleLoader {
pub lib: TypeLib,
/// The initial set of permissions used to resolve the static imports in the
/// worker. They are decoupled from the worker (dynamic) permissions since
@ -36,10 +33,7 @@ impl CliModuleLoader {
TypeLib::DenoWindow
};
let import_map = ps.maybe_import_map.clone();
Rc::new(CliModuleLoader {
import_map,
lib,
root_permissions: Permissions::allow_all(),
ps,
@ -54,7 +48,6 @@ impl CliModuleLoader {
};
Rc::new(CliModuleLoader {
import_map: None,
lib,
root_permissions: permissions,
ps,
@ -67,44 +60,25 @@ impl ModuleLoader for CliModuleLoader {
&self,
specifier: &str,
referrer: &str,
is_main: bool,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
// FIXME(bartlomieju): hacky way to provide compatibility with repl
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)
self.ps.resolve(specifier, referrer)
}
fn load(
&self,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
_is_dynamic: bool,
is_dynamic: bool,
) -> Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.clone();
let ps = self.ps.clone();
// NOTE: this block is async only because of `deno_core`
// interface requirements; module was already loaded
// when constructing module graph during call to `prepare_load`.
async move { ps.load(module_specifier, maybe_referrer) }.boxed_local()
// NOTE: this block is async only because of `deno_core` interface
// requirements; module was already loaded when constructing module graph
// during call to `prepare_load`.
async move { ps.load(module_specifier, maybe_referrer, is_dynamic) }
.boxed_local()
}
fn prepare_load(
@ -117,24 +91,31 @@ impl ModuleLoader for CliModuleLoader {
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
let specifier = specifier.clone();
let ps = self.ps.clone();
let maybe_import_map = self.import_map.clone();
let state = op_state.borrow();
let root_permissions = self.root_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);
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param
async move {
ps.prepare_module_load(
specifier,
vec![specifier],
is_dynamic,
lib,
root_permissions,
dynamic_permissions,
is_dynamic,
maybe_import_map,
)
.await
}

View file

@ -1,27 +1,30 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::module_graph::BundleType;
use crate::module_graph::EmitOptions;
use crate::module_graph::GraphBuilder;
use crate::cache;
use crate::config_file::IgnoredCompilerOptions;
use crate::diagnostics::Diagnostics;
use crate::emit;
use crate::errors::get_error_class_name;
use crate::proc_state::ProcState;
use crate::specifier_handler::FetchHandler;
use crate::specifier_handler::MemoryHandler;
use crate::specifier_handler::SpecifierHandler;
use crate::resolver::ImportMapResolver;
use deno_core::error::custom_error;
use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::error::Context;
use deno_core::parking_lot::Mutex;
use deno_core::resolve_url_or_path;
use deno_core::serde_json;
use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_graph;
use deno_runtime::permissions::Permissions;
use import_map::ImportMap;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::rc::Rc;
use std::sync::Arc;
@ -37,6 +40,15 @@ enum RuntimeBundleType {
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)]
#[serde(rename_all = "camelCase")]
struct EmitArgs {
@ -52,10 +64,21 @@ struct EmitArgs {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct EmitResult {
diagnostics: crate::diagnostics::Diagnostics,
diagnostics: Diagnostics,
files: HashMap<String, String>,
ignored_options: Option<crate::config_file::IgnoredCompilerOptions>,
stats: crate::module_graph::Stats,
#[serde(rename = "ignoredOptions")]
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(
@ -65,23 +88,19 @@ async fn op_emit(
) -> Result<EmitResult, AnyError> {
deno_runtime::ops::check_unstable2(&state, "Deno.emit");
let root_specifier = args.root_specifier;
let ps = state.borrow().borrow::<ProcState>().clone();
let mut runtime_permissions = {
let state = state.borrow();
state.borrow::<Permissions>().clone()
};
// when we are actually resolving modules without provided sources, we should
// 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)))
let state = state.borrow();
let ps = state.borrow::<ProcState>();
let mut runtime_permissions = { state.borrow::<Permissions>().clone() };
let mut cache: Box<dyn cache::CacherLoader> =
if let Some(sources) = &args.sources {
Box::new(cache::MemoryCacher::new(sources.clone()))
} else {
Arc::new(Mutex::new(FetchHandler::new(
&ps,
Box::new(cache::FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
runtime_permissions.clone(),
runtime_permissions.clone(),
)?))
))
};
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)
@ -107,37 +126,125 @@ async fn op_emit(
} else {
None
};
let mut builder = GraphBuilder::new(handler, maybe_import_map, None);
let root_specifier = resolve_url_or_path(&root_specifier)?;
builder.add(&root_specifier, false).await.map_err(|_| {
type_error(format!(
"Unable to handle the given specifier: {}",
&root_specifier
))
})?;
builder
.analyze_compiler_options(&args.compiler_options)
.await?;
let bundle_type = match args.bundle {
Some(RuntimeBundleType::Module) => BundleType::Module,
Some(RuntimeBundleType::Classic) => BundleType::Classic,
None => BundleType::None,
};
let graph = builder.get_graph();
let roots = vec![resolve_url_or_path(&root_specifier)?];
let maybe_imports =
to_maybe_imports(&roots[0], args.compiler_options.as_ref());
let maybe_resolver = maybe_import_map.as_ref().map(ImportMapResolver::new);
let graph = Arc::new(
deno_graph::create_graph(
roots,
true,
maybe_imports,
cache.as_mut_loader(),
maybe_resolver.as_ref().map(|r| r.as_resolver()),
None,
None,
)
.await,
);
// 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 graph_errors = graph.get_errors();
let (files, mut result_info) = graph.emit(EmitOptions {
bundle_type,
check: args.check.unwrap_or(true),
debug,
maybe_user_config: args.compiler_options,
})?;
result_info.diagnostics.extend_graph_errors(graph_errors);
let tsc_emit = check && args.bundle.is_none();
let (ts_config, maybe_ignored_options) = emit::get_ts_config(
emit::ConfigType::RuntimeEmit { tsc_emit },
None,
args.compiler_options.as_ref(),
)?;
let (files, mut diagnostics, stats) = if check && args.bundle.is_none() {
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 {
diagnostics: result_info.diagnostics,
diagnostics,
files,
ignored_options: result_info.maybe_ignored_options,
stats: result_info.stats,
maybe_ignored_options,
stats,
})
}

View file

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

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();
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
let (method, _) = client.read_notification::<Value>().unwrap();
assert_eq!(method, "textDocument/publishDiagnostics");
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);
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(), "mod6.bundle.js");
let file = PathBuf::from(&bundle);
@ -214,6 +215,7 @@ fn bundle_js_watch() {
write(&file_to_watch, "console.log('Hello world2');").unwrap();
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_to_watch.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
write(&file_to_watch, "console.log(42);").unwrap();
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_to_watch.js");
assert_contains!(stderr_lines.next().unwrap(), "target.js");

View file

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

View file

@ -1,53 +1,91 @@
{
"root": "file://[WILDCARD]/005_more_imports.ts",
"roots": [
"file://[WILDCARD]/005_more_imports.ts"
],
"modules": [
{
"specifier": "file://[WILDCARD]/005_more_imports.ts",
"dependencies": [
{
"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",
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/005_more_imports.ts"
},
{
"specifier": "file://[WILDCARD]/subdir/mod1.ts",
"dependencies": [
{
"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",
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/subdir/mod1.ts"
},
{
"specifier": "file://[WILDCARD]/subdir/print_hello.ts",
"dependencies": [],
"size": 57,
"mediaType": "TypeScript",
"local": "[WILDCARD]print_hello.ts",
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/subdir/print_hello.ts"
},
{
"specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts",
"dependencies": [
{
"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",
[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": [
{
"specifier": "file://[WILDCARD]/076_info_json_deps_order.ts",
"dependencies": [
{
"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",
"checksum": "5dd40fe33e5924cca513489ce568e86c9b9fe318a87975403c8923629018680d"
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/076_info_json_deps_order.ts"
},
{
"specifier": "file://[WILDCARD]/recursive_imports/A.ts",
"dependencies": [
{
"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",
"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",
"checksum": "3b45a105d892584298490cb73372b2cac57118e1e42a677a1d5cacea704d8d3a"
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/A.ts"
},
{
"specifier": "file://[WILDCARD]/recursive_imports/B.ts",
"dependencies": [
{
"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",
"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",
"checksum": "b12b0437ef9a91c4a4b1f66e8e4339f986b60bd8134031ccb296ce49df15b54e"
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/B.ts"
},
{
"specifier": "file://[WILDCARD]/recursive_imports/C.ts",
"dependencies": [
{
"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",
"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",
"checksum": "605875a410741bfaeeade28cbccf45f219ad99d987ea695e35eda75d2c53a658"
[WILDCARD]
"mediaType": "TypeScript",
[WILDCARD]
"specifier": "file://[WILDCARD]/recursive_imports/C.ts"
},
{
"specifier": "file://[WILDCARD]/recursive_imports/common.ts",
"dependencies": [],
"size": 28,
"mediaType": "TypeScript",
"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");
^
at [WILDCARD]
at async file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:1

View file

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

View file

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

View file

@ -1,6 +1,7 @@
[WILDCARD]
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.
[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({
name: "Deno.emit() - invalid syntax does not panic",
async fn() {
await assertThrowsAsync(async () => {
await Deno.emit("/main.js", {
sources: {
"/main.js": `
const { diagnostics } = await Deno.emit("/main.js", {
sources: {
"/main.js": `
export class Foo {
constructor() {
console.log("foo");
@ -325,9 +324,14 @@ Deno.test({
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({
name: "Deno.emit() - Unknown media type does not panic",
async fn() {
await assertThrowsAsync(async () => {
await Deno.emit("https://example.com/foo", {
sources: {
"https://example.com/foo": `let foo: string = "foo";`,
},
});
await Deno.emit("https://example.com/foo", {
sources: {
"https://example.com/foo": `let foo: string = "foo";`,
},
});
},
});
@ -487,7 +489,7 @@ Deno.test({
code: 900001,
start: null,
end: null,
messageText: "Unable to find specifier in sources: file:///b.ts",
messageText: 'Cannot load module "file:///b.ts".',
messageChain: null,
source: null,
sourceLine: null,
@ -497,7 +499,7 @@ Deno.test({
]);
assert(
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".
The following options were ignored:
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.
a() {
^

View file

@ -1,3 +1,4 @@
error: Modules imported via https are not allowed to import http modules.
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.
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
at blob:null/[WILDCARD]:1:0
at blob:null/[WILDCARD]:1:8
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
at blob:null/[WILDCARD]:1:0
at blob:null/[WILDCARD]:1:8
await import(URL.createObjectURL(blob));
^
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
at data:application/javascript;base64,[WILDCARD]:1:0
at data:application/javascript;base64,[WILDCARD]:1:8
await import(`data:application/javascript;base64,${btoa(code)}`);
^
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
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:0
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
await import(`data:application/javascript;base64,${btoa(code)}`);
^
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
at http://localhost:4545/dynamic_import/static_remote.ts:2:0
at http://localhost:4545/dynamic_import/static_remote.ts:2:8
await import(
^
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".
at file:///[WILDCARD]/error_004_missing_module.ts:1:0
[WILDCARD]error: Cannot load module "file:///[WILDCARD]/bad-module.ts".
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");
^
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".
at file:///[WILDCARD]/error_006_import_ext_failure.ts:1:0
[WILDCARD]error: Cannot load module "[WILDCARD]/non-existent".
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"
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
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");
^
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.
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.
TypeError: Relative import path "does not exist either" not prefixed with / or ./ or ../ from "[WILDCARD]/indirect_import_error.js"
at async file:///[WILDCARD]/error_014_catch_dynamic_import_error.js:10:5
TypeError: Relative import path "does not exist either" not prefixed with / or ./ or ../ from "[WILDCARD]/subdir/indirect_import_error.js"
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.
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.
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
at file://[WILDCARD]/error_015_dynamic_import_permissions.js:2:16
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]
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
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]
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
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]
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
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".
at [WILDCARD]/error_missing_module_named_import.ts:1:0
[WILDCARD]
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
at blob:null/[WILDCARD]:1:19
const a = await import(url);
^
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
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
type: JavaScript
dependencies: 1 unique (total 26B)
dependencies: 0 unique (total 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": [
{
"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
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]
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
Lock file: lock_check_err_with_bundle.json

View file

@ -1,4 +1,4 @@
[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
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]
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
[WILDCARD]
new SomeClass().test();
new SomeClass[WILDCARD].test();
[WILDCARD]

View file

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

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.
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
at blob:null/[WILDCARD]:1:0
at blob:null/[WILDCARD]:1:8
error: Uncaught (in promise) Error: Unhandled error event in child worker.
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
at blob:null/[WILDCARD]:1:0
at blob:null/[WILDCARD]:1:8
error: Uncaught (in promise) Error: Unhandled error event in child worker.
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
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.
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
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.
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
at http://localhost:4545/workers/dynamic_remote.ts:2:14
await import("https://example.com/some/file.ts");
^
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
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.
at Worker.#pollControl ([WILDCARD])

View file

@ -1,11 +1,12 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::emit;
use crate::flags::Flags;
use crate::fs_util::collect_files;
use crate::module_graph::TypeLib;
use crate::proc_state::ProcState;
use crate::source_maps::SourceMapGetter;
use deno_ast::swc::common::Span;
use deno_ast::MediaType;
use deno_core::error::AnyError;
@ -687,16 +688,15 @@ pub async fn cover_files(
let module_specifier =
deno_core::resolve_url_or_path(&script_coverage.url)?;
ps.prepare_module_load(
module_specifier.clone(),
TypeLib::UnstableDenoWindow,
Permissions::allow_all(),
Permissions::allow_all(),
vec![module_specifier.clone()],
false,
ps.maybe_import_map.clone(),
emit::TypeLib::UnstableDenoWindow,
Permissions::allow_all(),
Permissions::allow_all(),
)
.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 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 =
ModuleSpecifier::parse("deno://lib.deno.d.ts").unwrap();
let graph = create_graph(
source_file_specifier.clone(),
vec![source_file_specifier.clone()],
false,
None,
&mut loader,
None,
None,
@ -142,7 +144,9 @@ pub async fn print_docs(
import_map: ps.maybe_import_map.clone(),
};
let graph = create_graph(
root_specifier.clone(),
vec![root_specifier.clone()],
false,
None,
&mut loader,
Some(&resolver),
None,

View file

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

View file

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