summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/ast.rs12
-rw-r--r--cli/disk_cache.rs5
-rw-r--r--cli/file_fetcher.rs45
-rw-r--r--cli/fmt_errors.rs3
-rw-r--r--cli/main.rs14
-rw-r--r--cli/media_type.rs32
-rw-r--r--cli/module_graph2.rs766
-rw-r--r--cli/module_loader.rs41
-rw-r--r--cli/ops/errors.rs2
-rw-r--r--cli/program_state.rs506
-rw-r--r--cli/source_maps.rs19
-rw-r--r--cli/specifier_handler.rs160
-rw-r--r--cli/tests/020_json_modules.ts.out4
-rw-r--r--cli/tests/023_no_ext2
-rw-r--r--cli/tests/023_no_ext.out1
-rw-r--r--cli/tests/023_no_ext_with_headers1
-rw-r--r--cli/tests/023_no_ext_with_headers.out1
-rw-r--r--cli/tests/bundle/fixture14.out4
-rw-r--r--cli/tests/config.ts18
-rw-r--r--cli/tests/config.ts.out8
-rw-r--r--cli/tests/config.tsconfig.json2
-rw-r--r--cli/tests/disallow_http_from_https_js.out5
-rw-r--r--cli/tests/disallow_http_from_https_ts.out5
-rw-r--r--cli/tests/error_004_missing_module.ts.out4
-rw-r--r--cli/tests/error_005_missing_dynamic_import.ts.out2
-rw-r--r--cli/tests/error_006_import_ext_failure.ts.out2
-rw-r--r--cli/tests/error_011_bad_module_specifier.ts2
-rw-r--r--cli/tests/error_015_dynamic_import_permissions.out2
-rw-r--r--cli/tests/error_016_dynamic_import_permissions2.out5
-rw-r--r--cli/tests/error_local_static_import_from_remote.js.out5
-rw-r--r--cli/tests/error_local_static_import_from_remote.ts.out5
-rw-r--r--cli/tests/fix_exotic_specifiers.ts3
-rw-r--r--cli/tests/fix_exotic_specifiers.ts.out1
-rw-r--r--cli/tests/integration_tests.rs13
-rw-r--r--cli/tests/lock_check_err.out5
-rw-r--r--cli/tests/lock_check_err2.out5
-rw-r--r--cli/tests/lock_dynamic_imports.out5
-rw-r--r--cli/tests/module_graph/file_tests-importjson.ts3
-rw-r--r--cli/tests/module_graph/file_tests-some.json5
-rw-r--r--cli/tests/performance_stats.out26
-rw-r--r--cli/tests/single_compile_with_reload.ts.out1
-rw-r--r--cli/tests/ts_type_only_import.ts.out6
-rw-r--r--cli/tests/unsupported_dynamic_import_scheme.out2
-rw-r--r--cli/tsc.rs466
-rw-r--r--cli/tsc/99_main_compiler.js142
-rw-r--r--cli/tsc2.rs93
-rw-r--r--cli/tsc_config.rs15
-rw-r--r--cli/worker.rs2
48 files changed, 1179 insertions, 1297 deletions
diff --git a/cli/ast.rs b/cli/ast.rs
index 95f243717..78cafca1b 100644
--- a/cli/ast.rs
+++ b/cli/ast.rs
@@ -72,6 +72,18 @@ impl Into<Location> for swc_common::Loc {
}
}
+impl Into<ModuleSpecifier> for Location {
+ fn into(self) -> ModuleSpecifier {
+ ModuleSpecifier::resolve_url_or_path(&self.filename).unwrap()
+ }
+}
+
+impl std::fmt::Display for Location {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}:{}:{}", self.filename, self.line, self.col)
+ }
+}
+
/// A buffer for collecting diagnostic messages from the AST parser.
#[derive(Debug)]
pub struct DiagnosticBuffer(Vec<String>);
diff --git a/cli/disk_cache.rs b/cli/disk_cache.rs
index 8978065de..1fc9b3289 100644
--- a/cli/disk_cache.rs
+++ b/cli/disk_cache.rs
@@ -107,8 +107,9 @@ impl DiskCache {
}
scheme => {
unimplemented!(
- "Don't know how to create cache name for scheme: {}",
- scheme
+ "Don't know how to create cache name for scheme: {}\n Url: {}",
+ scheme,
+ url
);
}
};
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs
index 25e9e8835..c0a9c7227 100644
--- a/cli/file_fetcher.rs
+++ b/cli/file_fetcher.rs
@@ -579,6 +579,29 @@ fn map_js_like_extension(path: &Path, default: MediaType) -> MediaType {
None => default,
Some("jsx") => MediaType::JSX,
Some("tsx") => MediaType::TSX,
+ // Because DTS files do not have a separate media type, or a unique
+ // extension, we have to "guess" at those things that we consider that
+ // look like TypeScript, and end with `.d.ts` are DTS files.
+ Some("ts") => {
+ if default == MediaType::TypeScript {
+ match path.file_stem() {
+ None => default,
+ Some(os_str) => {
+ if let Some(file_stem) = os_str.to_str() {
+ if file_stem.ends_with(".d") {
+ MediaType::Dts
+ } else {
+ default
+ }
+ } else {
+ default
+ }
+ }
+ }
+ } else {
+ default
+ }
+ }
Some(_) => default,
},
}
@@ -1564,7 +1587,7 @@ mod tests {
);
assert_eq!(
map_content_type(Path::new("foo/bar.d.ts"), None).0,
- MediaType::TypeScript
+ MediaType::Dts
);
assert_eq!(
map_content_type(Path::new("foo/bar.js"), None).0,
@@ -1741,6 +1764,26 @@ mod tests {
.0,
MediaType::JSX
);
+ assert_eq!(
+ map_content_type(
+ Path::new("foo/bar.d.ts"),
+ Some("application/x-javascript")
+ )
+ .0,
+ MediaType::JavaScript
+ );
+ assert_eq!(
+ map_content_type(Path::new("foo/bar.d.ts"), Some("text/plain")).0,
+ MediaType::Dts
+ );
+ assert_eq!(
+ map_content_type(
+ Path::new("foo/bar.d.ts"),
+ Some("video/vnd.dlna.mpeg-tts"),
+ )
+ .0,
+ MediaType::Dts
+ );
}
#[test]
diff --git a/cli/fmt_errors.rs b/cli/fmt_errors.rs
index 333c47756..890f9b83f 100644
--- a/cli/fmt_errors.rs
+++ b/cli/fmt_errors.rs
@@ -7,6 +7,7 @@ use deno_core::error::{AnyError, JsError as CoreJsError, JsStackFrame};
use std::error::Error;
use std::fmt;
use std::ops::Deref;
+use std::sync::Arc;
const SOURCE_ABBREV_THRESHOLD: usize = 150;
@@ -237,7 +238,7 @@ pub struct JsError(CoreJsError);
impl JsError {
pub fn create(
core_js_error: CoreJsError,
- source_map_getter: &impl SourceMapGetter,
+ source_map_getter: Arc<impl SourceMapGetter>,
) -> AnyError {
let core_js_error = apply_source_map(&core_js_error, source_map_getter);
let js_error = Self(core_js_error);
diff --git a/cli/main.rs b/cli/main.rs
index 56e3c19f8..51355555e 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -51,7 +51,7 @@ mod test_runner;
mod text_encoding;
mod tokio_util;
mod tsc;
-pub mod tsc2;
+mod tsc2;
mod tsc_config;
mod upgrade;
mod version;
@@ -174,14 +174,16 @@ async fn info_command(
let specifier = ModuleSpecifier::resolve_url_or_path(&specifier)?;
let handler = Rc::new(RefCell::new(specifier_handler::FetchHandler::new(
&program_state,
+ // info accesses dynamically imported modules just for their information
+ // so we allow access to all of them.
Permissions::allow_all(),
)?));
let mut builder = module_graph2::GraphBuilder2::new(
handler,
program_state.maybe_import_map.clone(),
);
- builder.insert(&specifier).await?;
- let graph = builder.get_graph(&program_state.lockfile)?;
+ builder.add(&specifier, false).await?;
+ let graph = builder.get_graph(&program_state.lockfile);
let info = graph.info()?;
if json {
@@ -312,14 +314,16 @@ async fn bundle_command(
let output = if flags.no_check {
let handler = Rc::new(RefCell::new(FetchHandler::new(
&program_state,
+ // when bundling, dynamic imports are only access for their type safety,
+ // therefore we will allow the graph to access any module.
Permissions::allow_all(),
)?));
let mut builder = module_graph2::GraphBuilder2::new(
handler,
program_state.maybe_import_map.clone(),
);
- builder.insert(&module_specifier).await?;
- let graph = builder.get_graph(&program_state.lockfile)?;
+ builder.add(&module_specifier, false).await?;
+ let graph = builder.get_graph(&program_state.lockfile);
let (s, stats, maybe_ignored_options) =
graph.bundle(module_graph2::BundleOptions {
diff --git a/cli/media_type.rs b/cli/media_type.rs
index c3c2f8e23..7d63439f6 100644
--- a/cli/media_type.rs
+++ b/cli/media_type.rs
@@ -77,7 +77,19 @@ impl MediaType {
},
},
Some(os_str) => match os_str.to_str() {
- Some("ts") => MediaType::TypeScript,
+ Some("ts") => match path.file_stem() {
+ Some(os_str) => match os_str.to_str() {
+ Some(file_name) => {
+ if file_name.ends_with(".d") {
+ MediaType::Dts
+ } else {
+ MediaType::TypeScript
+ }
+ }
+ None => MediaType::TypeScript,
+ },
+ None => MediaType::TypeScript,
+ },
Some("tsx") => MediaType::TSX,
Some("js") => MediaType::JavaScript,
Some("jsx") => MediaType::JSX,
@@ -121,6 +133,19 @@ impl MediaType {
ext.into()
}
+
+ /// Map the media type to a `ts.ScriptKind`
+ pub fn as_ts_script_kind(&self) -> i32 {
+ match self {
+ MediaType::JavaScript => 1,
+ MediaType::JSX => 2,
+ MediaType::TypeScript => 3,
+ MediaType::Dts => 3,
+ MediaType::TSX => 4,
+ MediaType::Json => 5,
+ _ => 0,
+ }
+ }
}
impl Serialize for MediaType {
@@ -167,10 +192,7 @@ mod tests {
MediaType::TypeScript
);
assert_eq!(MediaType::from(Path::new("foo/bar.tsx")), MediaType::TSX);
- assert_eq!(
- MediaType::from(Path::new("foo/bar.d.ts")),
- MediaType::TypeScript
- );
+ assert_eq!(MediaType::from(Path::new("foo/bar.d.ts")), MediaType::Dts);
assert_eq!(
MediaType::from(Path::new("foo/bar.js")),
MediaType::JavaScript
diff --git a/cli/module_graph2.rs b/cli/module_graph2.rs
index e2dcdfefc..678fe8da5 100644
--- a/cli/module_graph2.rs
+++ b/cli/module_graph2.rs
@@ -6,18 +6,24 @@ use crate::ast::BundleHook;
use crate::ast::EmitOptions;
use crate::ast::Location;
use crate::ast::ParsedModule;
+use crate::colors;
+use crate::diagnostics::Diagnostics;
use crate::import_map::ImportMap;
use crate::info::ModuleGraphInfo;
use crate::info::ModuleInfo;
use crate::info::ModuleInfoMap;
use crate::info::ModuleInfoMapItem;
+use crate::js;
use crate::lockfile::Lockfile;
use crate::media_type::MediaType;
use crate::specifier_handler::CachedModule;
+use crate::specifier_handler::Dependency;
use crate::specifier_handler::DependencyMap;
use crate::specifier_handler::Emit;
use crate::specifier_handler::FetchFuture;
use crate::specifier_handler::SpecifierHandler;
+use crate::tsc2::exec;
+use crate::tsc2::Request;
use crate::tsc_config::IgnoredCompilerOptions;
use crate::tsc_config::TsConfig;
use crate::version;
@@ -26,7 +32,10 @@ use crate::AnyError;
use deno_core::error::Context;
use deno_core::futures::stream::FuturesUnordered;
use deno_core::futures::stream::StreamExt;
+use deno_core::serde::Serialize;
+use deno_core::serde::Serializer;
use deno_core::serde_json::json;
+use deno_core::ModuleResolutionError;
use deno_core::ModuleSpecifier;
use regex::Regex;
use serde::Deserialize;
@@ -62,7 +71,6 @@ lazy_static! {
/// A group of errors that represent errors that can occur when interacting with
/// a module graph.
-#[allow(unused)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum GraphError {
/// A module using the HTTPS protocol is trying to import a module with an
@@ -70,40 +78,37 @@ pub enum GraphError {
InvalidDowngrade(ModuleSpecifier, Location),
/// A remote module is trying to import a local module.
InvalidLocalImport(ModuleSpecifier, Location),
- /// A remote module is trying to import a local module.
- InvalidSource(ModuleSpecifier, String),
- /// A module specifier could not be resolved for a given import.
- InvalidSpecifier(String, Location),
+ /// The source code is invalid, as it does not match the expected hash in the
+ /// lockfile.
+ InvalidSource(ModuleSpecifier, PathBuf),
/// An unexpected dependency was requested for a module.
MissingDependency(ModuleSpecifier, String),
/// An unexpected specifier was requested.
MissingSpecifier(ModuleSpecifier),
- /// Snapshot data was not present in a situation where it was required.
- MissingSnapshotData,
/// The current feature is not supported.
NotSupported(String),
+ /// A unsupported media type was attempted to be imported as a module.
+ UnsupportedImportType(ModuleSpecifier, MediaType),
}
-use GraphError::*;
impl fmt::Display for GraphError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- InvalidDowngrade(ref specifier, ref location) => write!(f, "Modules imported via https are not allowed to import http modules.\n Importing: {}\n at {}:{}:{}", specifier, location.filename, location.line, location.col),
- InvalidLocalImport(ref specifier, ref location) => write!(f, "Remote modules are not allowed to import local modules.\n Importing: {}\n at {}:{}:{}", specifier, location.filename, location.line, location.col),
- InvalidSource(ref specifier, ref lockfile) => write!(f, "The source code is invalid, as it does not match the expected hash in the lock file.\n Specifier: {}\n Lock file: {}", specifier, lockfile),
- InvalidSpecifier(ref specifier, ref location) => write!(f, "Unable to resolve dependency specifier.\n Specifier: {}\n at {}:{}:{}", specifier, location.filename, location.line, location.col),
- MissingDependency(ref referrer, specifier) => write!(
+ GraphError::InvalidDowngrade(ref specifier, ref location) => write!(f, "Modules imported via https are not allowed to import http modules.\n Importing: {}\n at {}", specifier, location),
+ GraphError::InvalidLocalImport(ref specifier, ref location) => write!(f, "Remote modules are not allowed to import local modules. Consider using a dynamic import instead.\n Importing: {}\n at {}", specifier, location),
+ GraphError::InvalidSource(ref specifier, ref lockfile) => write!(f, "The source code is invalid, as it does not match the expected hash in the lock file.\n Specifier: {}\n Lock file: {}", specifier, lockfile.to_str().unwrap()),
+ GraphError::MissingDependency(ref referrer, specifier) => write!(
f,
"The graph is missing a dependency.\n Specifier: {} from {}",
specifier, referrer
),
- MissingSpecifier(ref specifier) => write!(
+ GraphError::MissingSpecifier(ref specifier) => write!(
f,
"The graph is missing a specifier.\n Specifier: {}",
specifier
),
- MissingSnapshotData => write!(f, "Snapshot data was not supplied, but required."),
- NotSupported(ref msg) => write!(f, "{}", msg),
+ GraphError::NotSupported(ref msg) => write!(f, "{}", msg),
+ GraphError::UnsupportedImportType(ref specifier, ref media_type) => write!(f, "An unsupported media type was attempted to be imported as a module.\n Specifier: {}\n MediaType: {}", specifier, media_type),
}
}
}
@@ -155,7 +160,10 @@ impl swc_bundler::Load for BundleLoader<'_> {
self.cm.clone(),
)
} else {
- Err(MissingDependency(specifier, "<bundle>".to_string()).into())
+ Err(
+ GraphError::MissingDependency(specifier, "<bundle>".to_string())
+ .into(),
+ )
}
}
_ => unreachable!("Received request for unsupported filename {:?}", file),
@@ -252,12 +260,24 @@ impl Default for Module {
impl Module {
pub fn new(
cached_module: CachedModule,
+ is_root: bool,
maybe_import_map: Option<Rc<RefCell<ImportMap>>>,
) -> Self {
+ // If this is a local root file, and its media type is unknown, set the
+ // media type to JavaScript. This allows easier ability to create "shell"
+ // scripts with Deno.
+ let media_type = if is_root
+ && !cached_module.is_remote
+ && cached_module.media_type == MediaType::Unknown
+ {
+ MediaType::JavaScript
+ } else {
+ cached_module.media_type
+ };
let mut module = Module {
specifier: cached_module.specifier,
maybe_import_map,
- media_type: cached_module.media_type,
+ media_type,
source: cached_module.source,
source_path: cached_module.source_path,
maybe_emit: cached_module.maybe_emit,
@@ -296,21 +316,28 @@ impl Module {
}
}
+ /// Parse a module, populating the structure with data retrieved from the
+ /// source of the module.
pub fn parse(&mut self) -> Result<(), AnyError> {
let parsed_module = parse(&self.specifier, &self.source, &self.media_type)?;
// parse out any triple slash references
for comment in parsed_module.get_leading_comments().iter() {
if let Some(ts_reference) = parse_ts_reference(&comment.text) {
- let location: Location = parsed_module.get_location(&comment.span);
+ let location = parsed_module.get_location(&comment.span);
match ts_reference {
TypeScriptReference::Path(import) => {
- let specifier = self.resolve_import(&import, Some(location))?;
- let dep = self.dependencies.entry(import).or_default();
+ let specifier =
+ self.resolve_import(&import, Some(location.clone()))?;
+ let dep = self
+ .dependencies
+ .entry(import)
+ .or_insert_with(|| Dependency::new(location));
dep.maybe_code = Some(specifier);
}
TypeScriptReference::Types(import) => {
- let specifier = self.resolve_import(&import, Some(location))?;
+ let specifier =
+ self.resolve_import(&import, Some(location.clone()))?;
if self.media_type == MediaType::JavaScript
|| self.media_type == MediaType::JSX
{
@@ -318,7 +345,10 @@ impl Module {
// this value changes
self.maybe_types = Some((import.clone(), specifier));
} else {
- let dep = self.dependencies.entry(import).or_default();
+ let dep = self
+ .dependencies
+ .entry(import)
+ .or_insert_with(|| Dependency::new(location));
dep.maybe_type = Some(specifier);
}
}
@@ -336,14 +366,30 @@ impl Module {
col: desc.col,
line: desc.line,
};
- let specifier =
- self.resolve_import(&desc.specifier, Some(location.clone()))?;
+
+ // In situations where there is a potential issue with resolving the
+ // import specifier, that ends up being a module resolution error for a
+ // code dependency, we should not throw in the `ModuleGraph` but instead
+ // wait until runtime and throw there, as with dynamic imports they need
+ // to be catchable, which means they need to be resolved at runtime.
+ let maybe_specifier =
+ match self.resolve_import(&desc.specifier, Some(location.clone())) {
+ Ok(specifier) => Some(specifier),
+ Err(any_error) => {
+ match any_error.downcast_ref::<ModuleResolutionError>() {
+ Some(ModuleResolutionError::ImportPrefixMissing(_, _)) => None,
+ _ => {
+ return Err(any_error);
+ }
+ }
+ }
+ };
// Parse out any `@deno-types` pragmas and modify dependency
- let maybe_types_specifier = if !desc.leading_comments.is_empty() {
+ let maybe_type = if !desc.leading_comments.is_empty() {
let comment = desc.leading_comments.last().unwrap();
if let Some(deno_types) = parse_deno_types(&comment.text).as_ref() {
- Some(self.resolve_import(deno_types, Some(location))?)
+ Some(self.resolve_import(deno_types, Some(location.clone()))?)
} else {
None
}
@@ -354,16 +400,21 @@ impl Module {
let dep = self
.dependencies
.entry(desc.specifier.to_string())
- .or_default();
- if desc.kind == swc_ecmascript::dep_graph::DependencyKind::ExportType
- || desc.kind == swc_ecmascript::dep_graph::DependencyKind::ImportType
- {
- dep.maybe_type = Some(specifier);
- } else {
- dep.maybe_code = Some(specifier);
+ .or_insert_with(|| Dependency::new(location));
+ dep.is_dynamic = desc.is_dynamic;
+ if let Some(specifier) = maybe_specifier {
+ if desc.kind == swc_ecmascript::dep_graph::DependencyKind::ExportType
+ || desc.kind == swc_ecmascript::dep_graph::DependencyKind::ImportType
+ {
+ dep.maybe_type = Some(specifier);
+ } else {
+ dep.maybe_code = Some(specifier);
+ }
}
- if let Some(types_specifier) = maybe_types_specifier {
- dep.maybe_type = Some(types_specifier);
+ // If the dependency wasn't a type only dependency already, and there is
+ // a `@deno-types` comment, then we will set the `maybe_type` dependency.
+ if maybe_type.is_some() && dep.maybe_type.is_none() {
+ dep.maybe_type = maybe_type;
}
}
@@ -400,14 +451,18 @@ impl Module {
// Disallow downgrades from HTTPS to HTTP
if referrer_scheme == "https" && specifier_scheme == "http" {
- return Err(InvalidDowngrade(specifier.clone(), location).into());
+ return Err(
+ GraphError::InvalidDowngrade(specifier.clone(), location).into(),
+ );
}
// Disallow a remote URL from trying to import a local URL
if (referrer_scheme == "https" || referrer_scheme == "http")
&& !(specifier_scheme == "https" || specifier_scheme == "http")
{
- return Err(InvalidLocalImport(specifier.clone(), location).into());
+ return Err(
+ GraphError::InvalidLocalImport(specifier.clone(), location).into(),
+ );
}
Ok(specifier)
@@ -438,20 +493,71 @@ impl<'de> Deserialize<'de> for Stats {
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() {
- write!(f, "{}: {}", key, value)?;
+ writeln!(f, " {}: {}", key, value)?;
}
Ok(())
}
}
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum TypeLib {
+ DenoWindow,
+ DenoWorker,
+ UnstableDenoWindow,
+ UnstableDenoWorker,
+}
+
+impl Default for TypeLib {
+ fn default() -> Self {
+ TypeLib::DenoWindow
+ }
+}
+
+impl Serialize for TypeLib {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let value = match self {
+ TypeLib::DenoWindow => vec!["deno.window".to_string()],
+ TypeLib::DenoWorker => vec!["deno.worker".to_string()],
+ TypeLib::UnstableDenoWindow => {
+ vec!["deno.window".to_string(), "deno.unstable".to_string()]
+ }
+ TypeLib::UnstableDenoWorker => {
+ vec!["deno.worker".to_string(), "deno.worker".to_string()]
+ }
+ };
+ Serialize::serialize(&value, serializer)
+ }
+}
+
#[derive(Debug, Default)]
pub struct BundleOptions {
pub debug: bool,
pub maybe_config_path: Option<String>,
}
+#[derive(Debug, Default)]
+pub struct CheckOptions {
+ /// If `true` then debug logging will be output from the isolate.
+ pub debug: bool,
+ /// Utilise the emit from `tsc` to update the emitted code for modules.
+ pub emit: bool,
+ /// The base type libraries that should be used when type checking.
+ pub lib: TypeLib,
+ /// An optional string that points to a user supplied TypeScript configuration
+ /// file that augments the the default configuration passed to the TypeScript
+ /// compiler.
+ pub maybe_config_path: Option<String>,
+ /// Ignore any previously emits and ensure that all files are emitted from
+ /// source.
+ pub reload: bool,
+}
+
/// A structure which provides options when transpiling modules.
#[derive(Debug, Default)]
pub struct TranspileOptions {
@@ -461,6 +567,9 @@ pub struct TranspileOptions {
/// file that augments the the default configuration passed to the TypeScript
/// compiler.
pub maybe_config_path: Option<String>,
+ /// Ignore any previously emits and ensure that all files are emitted from
+ /// source.
+ pub reload: bool,
}
/// A dependency graph of modules, were the modules that have been inserted via
@@ -468,11 +577,27 @@ pub struct TranspileOptions {
/// be able to manipulate and handle the graph.
#[derive(Debug)]
pub struct Graph2 {
+ /// A reference to the specifier handler that will retrieve and cache modules
+ /// for the graph.
handler: Rc<RefCell<dyn SpecifierHandler>>,
- maybe_ts_build_info: Option<String>,
+ /// Optional TypeScript build info that will be passed to `tsc` if `tsc` is
+ /// invoked.
+ maybe_tsbuildinfo: Option<String>,
+ /// The modules that are part of the graph.
modules: HashMap<ModuleSpecifier, Module>,
+ /// A map of redirects, where a module specifier is redirected to another
+ /// module specifier by the handler. All modules references should be
+ /// resolved internally via this, before attempting to access the module via
+ /// the handler, to make sure the correct modules is being dealt with.
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
+ /// The module specifiers that have been uniquely added to the graph, which
+ /// does not include any transient dependencies.
roots: Vec<ModuleSpecifier>,
+ /// If all of the root modules are dynamically imported, then this is true.
+ /// This is used to ensure correct `--reload` behavior, where subsequent
+ /// calls to a module graph where the emit is already valid do not cause the
+ /// graph to re-emit.
+ roots_dynamic: bool,
}
impl Graph2 {
@@ -484,10 +609,11 @@ impl Graph2 {
pub fn new(handler: Rc<RefCell<dyn SpecifierHandler>>) -> Self {
Graph2 {
handler,
- maybe_ts_build_info: None,
+ maybe_tsbuildinfo: None,
modules: HashMap::new(),
redirects: HashMap::new(),
roots: Vec::new(),
+ roots_dynamic: true,
}
}
@@ -498,7 +624,7 @@ impl Graph2 {
options: BundleOptions,
) -> Result<(String, Stats, Option<IgnoredCompilerOptions>), AnyError> {
if self.roots.is_empty() || self.roots.len() > 1 {
- return Err(NotSupported(format!("Bundling is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into());
+ return Err(GraphError::NotSupported(format!("Bundling is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into());
}
let start = Instant::now();
@@ -566,6 +692,141 @@ impl Graph2 {
self.modules.contains_key(s)
}
+ /// Type check the module graph, corresponding to the options provided.
+ pub fn check(
+ self,
+ options: CheckOptions,
+ ) -> Result<(Stats, Diagnostics, Option<IgnoredCompilerOptions>), AnyError>
+ {
+ // TODO(@kitsonk) set to `true` in followup PR
+ let unstable = options.lib == TypeLib::UnstableDenoWindow
+ || options.lib == TypeLib::UnstableDenoWorker;
+ let mut config = TsConfig::new(json!({
+ "allowJs": true,
+ // TODO(@kitsonk) is this really needed?
+ "esModuleInterop": true,
+ // Enabled by default to align to transpile/swc defaults
+ "experimentalDecorators": true,
+ "incremental": true,
+ "isolatedModules": unstable,
+ "lib": options.lib,
+ "module": "esnext",
+ "strict": true,
+ "target": "esnext",
+ "tsBuildInfoFile": "deno:///.tsbuildinfo",
+ }));
+ if options.emit {
+ config.merge(&json!({
+ // TODO(@kitsonk) consider enabling this by default
+ // see: https://github.com/denoland/deno/issues/7732
+ "emitDecoratorMetadata": false,
+ "jsx": "react",
+ "inlineSourceMap": true,
+ "outDir": "deno://",
+ "removeComments": true,
+ }));
+ } else {
+ config.merge(&json!({
+ "noEmit": true,
+ }));
+ }
+ let maybe_ignored_options =
+ config.merge_user_config(options.maybe_config_path)?;
+
+ // Short circuit if none of the modules require an emit, or all of the
+ // modules that require an emit have a valid emit. There is also an edge
+ // case where there are multiple imports of a dynamic module during a
+ // single invocation, if that is the case, even if there is a reload, we
+ // will simply look at if the emit is invalid, to avoid two checks for the
+ // same programme.
+ if !self.needs_emit(&config)
+ || (self.is_emit_valid(&config)
+ && (!options.reload || self.roots_dynamic))
+ {
+ debug!("graph does not need to be checked or emitted.");
+ return Ok((
+ Stats(Vec::new()),
+ Diagnostics(Vec::new()),
+ maybe_ignored_options,
+ ));
+ }
+
+ // TODO(@kitsonk) not totally happy with this here, but this is the first
+ // point where we know we are actually going to check the program. If we
+ // moved it out of here, we wouldn't know until after the check has already
+ // happened, which isn't informative to the users.
+ for specifier in &self.roots {
+ info!("{} {}", colors::green("Check"), specifier);
+ }
+
+ let root_names: Vec<String> =
+ self.roots.iter().map(|ms| ms.to_string()).collect();
+ let maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone();
+ let hash_data =
+ vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
+ let graph = Rc::new(RefCell::new(self));
+
+ let response = exec(
+ js::compiler_isolate_init(),
+ Request {
+ config: config.clone(),
+ debug: options.debug,
+ graph: graph.clone(),
+ hash_data,
+ maybe_tsbuildinfo,
+ root_names,
+ },
+ )?;
+
+ let mut graph = graph.borrow_mut();
+ graph.maybe_tsbuildinfo = response.maybe_tsbuildinfo;
+ // Only process changes to the graph if there are no diagnostics and there
+ // were files emitted.
+ if response.diagnostics.0.is_empty() && !response.emitted_files.is_empty() {
+ let mut codes = HashMap::new();
+ let mut maps = HashMap::new();
+ let check_js = config.get_check_js();
+ for emit in &response.emitted_files {
+ if let Some(specifiers) = &emit.maybe_specifiers {
+ assert!(specifiers.len() == 1, "Unexpected specifier length");
+ let specifier = specifiers[0].clone();
+ // Sometimes if tsc sees a CommonJS file it will _helpfully_ output it
+ // to ESM, which we don't really want unless someone has enabled the
+ // check_js option.
+ if !check_js
+ && graph.get_media_type(&specifier) == Some(MediaType::JavaScript)
+ {
+ debug!("skipping emit for {}", specifier);
+ continue;
+ }
+ match emit.media_type {
+ MediaType::JavaScript => {
+ codes.insert(specifier, emit.data.clone());
+ }
+ MediaType::SourceMap => {
+ maps.insert(specifier, emit.data.clone());
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+ let config = config.as_bytes();
+ for (specifier, code) in codes.iter() {
+ if let Some(module) = graph.get_module_mut(specifier) {
+ module.maybe_emit =
+ Some(Emit::Cli((code.clone(), maps.get(specifier).cloned())));
+ module.set_version(&config);
+ module.is_dirty = true;
+ } else {
+ return Err(GraphError::MissingSpecifier(specifier.clone()).into());
+ }
+ }
+ }
+ graph.flush()?;
+
+ Ok((response.stats, response.diagnostics, maybe_ignored_options))
+ }
+
/// Update the handler with any modules that are marked as _dirty_ and update
/// any build info if present.
fn flush(&mut self) -> Result<(), AnyError> {
@@ -582,8 +843,8 @@ impl Graph2 {
}
}
for root_specifier in self.roots.iter() {
- if let Some(ts_build_info) = &self.maybe_ts_build_info {
- handler.set_ts_build_info(root_specifier, ts_build_info.to_owned())?;
+ if let Some(tsbuildinfo) = &self.maybe_tsbuildinfo {
+ handler.set_tsbuildinfo(root_specifier, tsbuildinfo.to_owned())?;
}
}
@@ -694,12 +955,26 @@ impl Graph2 {
}
}
+ fn get_module_mut(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<&mut Module> {
+ // this is duplicated code because `.resolve_specifier` requires an
+ // immutable borrow, but if `.resolve_specifier` is mut, then everything
+ // that calls it is is mut
+ let mut s = specifier;
+ while let Some(redirect) = self.redirects.get(s) {
+ s = redirect;
+ }
+ self.modules.get_mut(s)
+ }
+
/// Return a structure which provides information about the module graph and
/// the relationship of the modules in the graph. This structure is used to
/// provide information for the `info` subcommand.
pub fn info(&self) -> Result<ModuleGraphInfo, AnyError> {
if self.roots.is_empty() || self.roots.len() > 1 {
- return Err(NotSupported(format!("Info is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into());
+ return Err(GraphError::NotSupported(format!("Info is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into());
}
let module = self.roots[0].clone();
@@ -731,72 +1006,124 @@ impl Graph2 {
})
}
+ /// Determines if all of the modules in the graph that require an emit have
+ /// a valid emit. Returns `true` if all the modules have a valid emit,
+ /// otherwise false.
+ fn is_emit_valid(&self, config: &TsConfig) -> bool {
+ let check_js = config.get_check_js();
+ let config = config.as_bytes();
+ self.modules.iter().all(|(_, m)| {
+ let needs_emit = match m.media_type {
+ MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true,
+ MediaType::JavaScript => check_js,
+ _ => false,
+ };
+ if needs_emit {
+ m.is_emit_valid(&config)
+ } else {
+ true
+ }
+ })
+ }
+
/// Verify the subresource integrity of the graph based upon the optional
/// lockfile, updating the lockfile with any missing resources. This will
/// error if any of the resources do not match their lock status.
- pub fn lock(
- &self,
- maybe_lockfile: &Option<Mutex<Lockfile>>,
- ) -> Result<(), AnyError> {
+ pub fn lock(&self, maybe_lockfile: &Option<Mutex<Lockfile>>) {
if let Some(lf) = maybe_lockfile {
let mut lockfile = lf.lock().unwrap();
for (ms, module) in self.modules.iter() {
let specifier = module.specifier.to_string();
let valid = lockfile.check_or_insert(&specifier, &module.source);
if !valid {
- return Err(
- InvalidSource(ms.clone(), lockfile.filename.display().to_string())
- .into(),
+ eprintln!(
+ "{}",
+ GraphError::InvalidSource(ms.clone(), lockfile.filename.clone())
);
+ std::process::exit(10);
}
}
}
+ }
- Ok(())
+ /// Determines if any of the modules in the graph are required to be emitted.
+ /// This is similar to `emit_valid()` except that the actual emit isn't
+ /// checked to determine if it is valid.
+ fn needs_emit(&self, config: &TsConfig) -> bool {
+ let check_js = config.get_check_js();
+ self.modules.iter().any(|(_, m)| match m.media_type {
+ MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true,
+ MediaType::JavaScript => check_js,
+ _ => false,
+ })
}
/// Given a string specifier and a referring module specifier, provide the
/// resulting module specifier and media type for the module that is part of
/// the graph.
+ ///
+ /// # Arguments
+ ///
+ /// * `specifier` - The string form of the module specifier that needs to be
+ /// resolved.
+ /// * `referrer` - The referring `ModuleSpecifier`.
+ /// * `prefer_types` - When resolving to a module specifier, determine if a
+ /// type dependency is preferred over a code dependency. This is set to
+ /// `true` when resolving module names for `tsc` as it needs the type
+ /// dependency over the code, while other consumers do not handle type only
+ /// dependencies.
pub fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
+ prefer_types: bool,
) -> Result<ModuleSpecifier, AnyError> {
if !self.contains_module(referrer) {
- return Err(MissingSpecifier(referrer.to_owned()).into());
+ return Err(GraphError::MissingSpecifier(referrer.to_owned()).into());
}
let module = self.get_module(referrer).unwrap();
if !module.dependencies.contains_key(specifier) {
return Err(
- MissingDependency(referrer.to_owned(), specifier.to_owned()).into(),
+ GraphError::MissingDependency(
+ referrer.to_owned(),
+ specifier.to_owned(),
+ )
+ .into(),
);
}
let dependency = module.dependencies.get(specifier).unwrap();
// If there is a @deno-types pragma that impacts the dependency, then the
// maybe_type property will be set with that specifier, otherwise we use the
// specifier that point to the runtime code.
- let resolved_specifier =
- if let Some(type_specifier) = dependency.maybe_type.clone() {
- type_specifier
- } else if let Some(code_specifier) = dependency.maybe_code.clone() {
- code_specifier
- } else {
- return Err(
- MissingDependency(referrer.to_owned(), specifier.to_owned()).into(),
- );
- };
+ let resolved_specifier = if prefer_types && dependency.maybe_type.is_some()
+ {
+ dependency.maybe_type.clone().unwrap()
+ } else if let Some(code_specifier) = dependency.maybe_code.clone() {
+ code_specifier
+ } else {
+ return Err(
+ GraphError::MissingDependency(
+ referrer.to_owned(),
+ specifier.to_owned(),
+ )
+ .into(),
+ );
+ };
if !self.contains_module(&resolved_specifier) {
return Err(
- MissingDependency(referrer.to_owned(), resolved_specifier.to_string())
- .into(),
+ GraphError::MissingDependency(
+ referrer.to_owned(),
+ resolved_specifier.to_string(),
+ )
+ .into(),
);
}
let dep_module = self.get_module(&resolved_specifier).unwrap();
// In the case that there is a X-TypeScript-Types or a triple-slash types,
// then the `maybe_types` specifier will be populated and we should use that
// instead.
- let result = if let Some((_, types)) = dep_module.maybe_types.clone() {
+ let result = if prefer_types && dep_module.maybe_types.is_some() {
+ let (_, types) = dep_module.maybe_types.clone().unwrap();
types
} else {
resolved_specifier
@@ -835,7 +1162,7 @@ impl Graph2 {
///
/// # Arguments
///
- /// - `options` - A structure of options which impact how the code is
+ /// * `options` - A structure of options which impact how the code is
/// transpiled.
///
pub fn transpile(
@@ -858,6 +1185,7 @@ impl Graph2 {
let emit_options: EmitOptions = ts_config.clone().into();
let mut emit_count: u128 = 0;
+ let config = ts_config.as_bytes();
for (_, module) in self.modules.iter_mut() {
// TODO(kitsonk) a lot of this logic should be refactored into `Module` as
// we start to support other methods on the graph. Especially managing
@@ -875,9 +1203,8 @@ impl Graph2 {
{
continue;
}
- let config = ts_config.as_bytes();
// skip modules that already have a valid emit
- if module.maybe_emit.is_some() && module.is_emit_valid(&config) {
+ if !options.reload && module.is_emit_valid(&config) {
continue;
}
if module.maybe_parsed_module.is_none() {
@@ -917,7 +1244,7 @@ impl swc_bundler::Resolve for Graph2 {
referrer
)
};
- let specifier = self.resolve(specifier, &referrer)?;
+ let specifier = self.resolve(specifier, &referrer, false)?;
Ok(swc_common::FileName::Custom(specifier.to_string()))
}
@@ -949,15 +1276,55 @@ impl GraphBuilder2 {
}
}
+ /// Add a module into the graph based on a module specifier. The module
+ /// and any dependencies will be fetched from the handler. The module will
+ /// also be treated as a _root_ module in the graph.
+ pub async fn add(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ is_dynamic: bool,
+ ) -> Result<(), AnyError> {
+ self.fetch(specifier, &None, is_dynamic)?;
+
+ loop {
+ let cached_module = self.pending.next().await.unwrap()?;
+ let is_root = &cached_module.specifier == specifier;
+ self.visit(cached_module, is_root)?;
+ if self.pending.is_empty() {
+ break;
+ }
+ }
+
+ if !self.graph.roots.contains(specifier) {
+ self.graph.roots.push(specifier.clone());
+ self.graph.roots_dynamic = self.graph.roots_dynamic && is_dynamic;
+ if self.graph.maybe_tsbuildinfo.is_none() {
+ let handler = self.graph.handler.borrow();
+ self.graph.maybe_tsbuildinfo = handler.get_tsbuildinfo(specifier)?;
+ }
+ }
+
+ Ok(())
+ }
+
/// Request a module to be fetched from the handler and queue up its future
/// to be awaited to be resolved.
- fn fetch(&mut self, specifier: &ModuleSpecifier) -> Result<(), AnyError> {
+ fn fetch(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ maybe_referrer: &Option<Location>,
+ is_dynamic: bool,
+ ) -> Result<(), AnyError> {
if self.fetched.contains(&specifier) {
return Ok(());
}
self.fetched.insert(specifier.clone());
- let future = self.graph.handler.borrow_mut().fetch(specifier.clone());
+ let future = self.graph.handler.borrow_mut().fetch(
+ specifier.clone(),
+ maybe_referrer.clone(),
+ is_dynamic,
+ );
self.pending.push(future);
Ok(())
@@ -966,10 +1333,30 @@ impl GraphBuilder2 {
/// Visit a module that has been fetched, hydrating the module, analyzing its
/// dependencies if required, fetching those dependencies, and inserting the
/// module into the graph.
- fn visit(&mut self, cached_module: CachedModule) -> Result<(), AnyError> {
+ fn visit(
+ &mut self,
+ cached_module: CachedModule,
+ is_root: bool,
+ ) -> Result<(), AnyError> {
let specifier = cached_module.specifier.clone();
let requested_specifier = cached_module.requested_specifier.clone();
- let mut module = Module::new(cached_module, self.maybe_import_map.clone());
+ let mut module =
+ Module::new(cached_module, is_root, self.maybe_import_map.clone());
+ match module.media_type {
+ MediaType::Json
+ | MediaType::SourceMap
+ | MediaType::TsBuildInfo
+ | MediaType::Unknown => {
+ return Err(
+ GraphError::UnsupportedImportType(
+ module.specifier,
+ module.media_type,
+ )
+ .into(),
+ );
+ }
+ _ => (),
+ }
if !module.is_parsed {
let has_types = module.maybe_types.is_some();
module.parse()?;
@@ -984,15 +1371,16 @@ impl GraphBuilder2 {
}
}
for (_, dep) in module.dependencies.iter() {
+ let maybe_referrer = Some(dep.location.clone());
if let Some(specifier) = dep.maybe_code.as_ref() {
- self.fetch(specifier)?;
+ self.fetch(specifier, &maybe_referrer, dep.is_dynamic)?;
}
if let Some(specifier) = dep.maybe_type.as_ref() {
- self.fetch(specifier)?;
+ self.fetch(specifier, &maybe_referrer, dep.is_dynamic)?;
}
}
if let Some((_, specifier)) = module.maybe_types.as_ref() {
- self.fetch(specifier)?;
+ self.fetch(specifier, &None, false)?;
}
if specifier != requested_specifier {
self
@@ -1005,45 +1393,17 @@ impl GraphBuilder2 {
Ok(())
}
- /// Insert a module into the graph based on a module specifier. The module
- /// and any dependencies will be fetched from the handler. The module will
- /// also be treated as a _root_ module in the graph.
- pub async fn insert(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Result<(), AnyError> {
- self.fetch(specifier)?;
-
- loop {
- let cached_module = self.pending.next().await.unwrap()?;
- self.visit(cached_module)?;
- if self.pending.is_empty() {
- break;
- }
- }
-
- if !self.graph.roots.contains(specifier) {
- self.graph.roots.push(specifier.clone());
- }
-
- Ok(())
- }
-
/// Move out the graph from the builder to be utilized further. An optional
/// lockfile can be provided, where if the sources in the graph do not match
- /// the expected lockfile, the method with error instead of returning the
- /// graph.
+ /// the expected lockfile, an error will be logged and the process will exit.
///
/// TODO(@kitsonk) this should really be owned by the graph, but currently
/// the lockfile is behind a mutex in program_state, which makes it really
/// hard to not pass around as a reference, which if the Graph owned it, it
/// would need lifetime parameters and lifetime parameters are 😭
- pub fn get_graph(
- self,
- maybe_lockfile: &Option<Mutex<Lockfile>>,
- ) -> Result<Graph2, AnyError> {
- self.graph.lock(maybe_lockfile)?;
- Ok(self.graph)
+ pub fn get_graph(self, maybe_lockfile: &Option<Mutex<Lockfile>>) -> Graph2 {
+ self.graph.lock(maybe_lockfile);
+ self.graph
}
}
@@ -1063,8 +1423,8 @@ pub mod tests {
#[derive(Debug, Default)]
pub struct MockSpecifierHandler {
pub fixtures: PathBuf,
- pub maybe_ts_build_info: Option<String>,
- pub ts_build_info_calls: Vec<(ModuleSpecifier, String)>,
+ pub maybe_tsbuildinfo: Option<String>,
+ pub tsbuildinfo_calls: Vec<(ModuleSpecifier, String)>,
pub cache_calls: Vec<(ModuleSpecifier, Emit)>,
pub deps_calls: Vec<(ModuleSpecifier, DependencyMap)>,
pub types_calls: Vec<(ModuleSpecifier, String)>,
@@ -1097,6 +1457,7 @@ pub mod tests {
_ => MediaType::Unknown,
};
let source = fs::read_to_string(&source_path)?;
+ let is_remote = specifier.as_url().scheme() != "file";
Ok(CachedModule {
source,
@@ -1104,20 +1465,26 @@ pub mod tests {
source_path,
specifier,
media_type,
+ is_remote,
..CachedModule::default()
})
}
}
impl SpecifierHandler for MockSpecifierHandler {
- fn fetch(&mut self, specifier: ModuleSpecifier) -> FetchFuture {
+ fn fetch(
+ &mut self,
+ specifier: ModuleSpecifier,
+ _maybe_referrer: Option<Location>,
+ _is_dynamic: bool,
+ ) -> FetchFuture {
Box::pin(future::ready(self.get_cache(specifier)))
}
- fn get_ts_build_info(
+ fn get_tsbuildinfo(
&self,
_specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError> {
- Ok(self.maybe_ts_build_info.clone())
+ Ok(self.maybe_tsbuildinfo.clone())
}
fn set_cache(
&mut self,
@@ -1135,15 +1502,15 @@ pub mod tests {
self.types_calls.push((specifier.clone(), types));
Ok(())
}
- fn set_ts_build_info(
+ fn set_tsbuildinfo(
&mut self,
specifier: &ModuleSpecifier,
- ts_build_info: String,
+ tsbuildinfo: String,
) -> Result<(), AnyError> {
- self.maybe_ts_build_info = Some(ts_build_info.clone());
+ self.maybe_tsbuildinfo = Some(tsbuildinfo.clone());
self
- .ts_build_info_calls
- .push((specifier.clone(), ts_build_info));
+ .tsbuildinfo_calls
+ .push((specifier.clone(), tsbuildinfo));
Ok(())
}
fn set_deps(
@@ -1164,6 +1531,24 @@ pub mod tests {
}
}
+ async fn setup(
+ specifier: ModuleSpecifier,
+ ) -> (Graph2, Rc<RefCell<MockSpecifierHandler>>) {
+ let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
+ let fixtures = c.join("tests/module_graph");
+ let handler = Rc::new(RefCell::new(MockSpecifierHandler {
+ fixtures,
+ ..MockSpecifierHandler::default()
+ }));
+ let mut builder = GraphBuilder2::new(handler.clone(), None);
+ builder
+ .add(&specifier, false)
+ .await
+ .expect("module not inserted");
+
+ (builder.get_graph(&None), handler)
+ }
+
#[test]
fn test_get_version() {
let doc_a = "console.log(42);";
@@ -1265,10 +1650,10 @@ pub mod tests {
}));
let mut builder = GraphBuilder2::new(handler.clone(), None);
builder
- .insert(&specifier)
+ .add(&specifier, false)
.await
.expect("module not inserted");
- let graph = builder.get_graph(&None).expect("could not get graph");
+ let graph = builder.get_graph(&None);
let (actual, stats, maybe_ignored_options) = graph
.bundle(BundleOptions::default())
.expect("could not bundle");
@@ -1281,22 +1666,57 @@ pub mod tests {
}
#[tokio::test]
+ async fn test_graph_check_emit() {
+ let specifier =
+ ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
+ .expect("could not resolve module");
+ let (graph, handler) = setup(specifier).await;
+ let (stats, diagnostics, maybe_ignored_options) = graph
+ .check(CheckOptions {
+ debug: false,
+ emit: true,
+ lib: TypeLib::DenoWindow,
+ maybe_config_path: None,
+ reload: false,
+ })
+ .expect("should have checked");
+ assert!(maybe_ignored_options.is_none());
+ assert_eq!(stats.0.len(), 12);
+ assert!(diagnostics.0.is_empty());
+ let h = handler.borrow();
+ assert_eq!(h.cache_calls.len(), 2);
+ assert_eq!(h.tsbuildinfo_calls.len(), 1);
+ }
+
+ #[tokio::test]
+ async fn test_graph_check_no_emit() {
+ let specifier =
+ ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
+ .expect("could not resolve module");
+ let (graph, handler) = setup(specifier).await;
+ let (stats, diagnostics, maybe_ignored_options) = graph
+ .check(CheckOptions {
+ debug: false,
+ emit: false,
+ lib: TypeLib::DenoWindow,
+ maybe_config_path: None,
+ reload: false,
+ })
+ .expect("should have checked");
+ assert!(maybe_ignored_options.is_none());
+ assert_eq!(stats.0.len(), 12);
+ assert!(diagnostics.0.is_empty());
+ let h = handler.borrow();
+ assert_eq!(h.cache_calls.len(), 0);
+ assert_eq!(h.tsbuildinfo_calls.len(), 1);
+ }
+
+ #[tokio::test]
async fn test_graph_info() {
- let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
- let fixtures = c.join("tests/module_graph");
- let handler = Rc::new(RefCell::new(MockSpecifierHandler {
- fixtures,
- ..MockSpecifierHandler::default()
- }));
- let mut builder = GraphBuilder2::new(handler.clone(), None);
let specifier =
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
.expect("could not resolve module");
- builder
- .insert(&specifier)
- .await
- .expect("module not inserted");
- let graph = builder.get_graph(&None).expect("could not get graph");
+ let (graph, _) = setup(specifier).await;
let info = graph.info().expect("could not get info");
assert!(info.compiled.is_none());
assert_eq!(info.dep_count, 6);
@@ -1312,6 +1732,24 @@ pub mod tests {
}
#[tokio::test]
+ async fn test_graph_import_json() {
+ let specifier =
+ ModuleSpecifier::resolve_url_or_path("file:///tests/importjson.ts")
+ .expect("could not resolve module");
+ let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
+ let fixtures = c.join("tests/module_graph");
+ let handler = Rc::new(RefCell::new(MockSpecifierHandler {
+ fixtures,
+ ..MockSpecifierHandler::default()
+ }));
+ let mut builder = GraphBuilder2::new(handler.clone(), None);
+ builder
+ .add(&specifier, false)
+ .await
+ .expect_err("should have errored");
+ }
+
+ #[tokio::test]
async fn test_graph_transpile() {
// This is a complex scenario of transpiling, where we have TypeScript
// importing a JavaScript file (with type definitions) which imports
@@ -1320,21 +1758,10 @@ pub mod tests {
// to be actually emitted.
//
// This also exercises "@deno-types" and type references.
- let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
- let fixtures = c.join("tests/module_graph");
- let handler = Rc::new(RefCell::new(MockSpecifierHandler {
- fixtures,
- ..MockSpecifierHandler::default()
- }));
- let mut builder = GraphBuilder2::new(handler.clone(), None);
let specifier =
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
.expect("could not resolve module");
- builder
- .insert(&specifier)
- .await
- .expect("module not inserted");
- let mut graph = builder.get_graph(&None).expect("could not get graph");
+ let (mut graph, handler) = setup(specifier).await;
let (stats, maybe_ignored_options) =
graph.transpile(TranspileOptions::default()).unwrap();
assert_eq!(stats.0.len(), 3);
@@ -1385,25 +1812,15 @@ pub mod tests {
#[tokio::test]
async fn test_graph_transpile_user_config() {
- let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
- let fixtures = c.join("tests/module_graph");
- let handler = Rc::new(RefCell::new(MockSpecifierHandler {
- fixtures: fixtures.clone(),
- ..MockSpecifierHandler::default()
- }));
- let mut builder = GraphBuilder2::new(handler.clone(), None);
let specifier =
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/transpile.tsx")
.expect("could not resolve module");
- builder
- .insert(&specifier)
- .await
- .expect("module not inserted");
- let mut graph = builder.get_graph(&None).expect("could not get graph");
+ let (mut graph, handler) = setup(specifier).await;
let (_, maybe_ignored_options) = graph
.transpile(TranspileOptions {
debug: false,
maybe_config_path: Some("tests/module_graph/tsconfig.json".to_string()),
+ reload: false,
})
.unwrap();
assert_eq!(
@@ -1441,36 +1858,9 @@ pub mod tests {
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
.expect("could not resolve module");
builder
- .insert(&specifier)
+ .add(&specifier, false)
.await
.expect("module not inserted");
- builder
- .get_graph(&maybe_lockfile)
- .expect("could not get graph");
- }
-
- #[tokio::test]
- async fn test_graph_with_lockfile_fail() {
- let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
- let fixtures = c.join("tests/module_graph");
- let lockfile_path = fixtures.join("lockfile_fail.json");
- let lockfile =
- Lockfile::new(lockfile_path, false).expect("could not load lockfile");
- let maybe_lockfile = Some(Mutex::new(lockfile));
- let handler = Rc::new(RefCell::new(MockSpecifierHandler {
- fixtures,
- ..MockSpecifierHandler::default()
- }));
- let mut builder = GraphBuilder2::new(handler.clone(), None);
- let specifier =
- ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
- .expect("could not resolve module");
- builder
- .insert(&specifier)
- .await
- .expect("module not inserted");
- builder
- .get_graph(&maybe_lockfile)
- .expect_err("expected an error");
+ builder.get_graph(&maybe_lockfile);
}
}
diff --git a/cli/module_loader.rs b/cli/module_loader.rs
index 3cbcade2a..39690465c 100644
--- a/cli/module_loader.rs
+++ b/cli/module_loader.rs
@@ -83,7 +83,7 @@ impl ModuleLoader for CliModuleLoader {
op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
- _is_dyn_import: bool,
+ _is_dynamic: bool,
) -> Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.to_owned();
let module_url_specified = module_specifier.to_string();
@@ -92,11 +92,10 @@ impl ModuleLoader for CliModuleLoader {
state.borrow::<Arc<ProgramState>>().clone()
};
- // TODO(bartlomieju): `fetch_compiled_module` should take `load_id` param
+ // TODO(@kitsonk) this shouldn't be async
let fut = async move {
let compiled_module = program_state
- .fetch_compiled_module(module_specifier, maybe_referrer)
- .await?;
+ .fetch_compiled_module(module_specifier, maybe_referrer)?;
Ok(deno_core::ModuleSource {
// Real module name, might be different from initial specifier
// due to redirections.
@@ -113,44 +112,28 @@ impl ModuleLoader for CliModuleLoader {
&self,
op_state: Rc<RefCell<OpState>>,
_load_id: ModuleLoadId,
- module_specifier: &ModuleSpecifier,
- maybe_referrer: Option<String>,
- is_dyn_import: bool,
+ specifier: &ModuleSpecifier,
+ _maybe_referrer: Option<String>,
+ is_dynamic: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
- let module_specifier = module_specifier.clone();
+ let specifier = specifier.clone();
let target_lib = self.target_lib.clone();
let maybe_import_map = self.import_map.clone();
let state = op_state.borrow();
- // Only "main" module is loaded without permission check,
- // ie. module that is associated with "is_main" state
- // and is not a dynamic import.
- let permissions = if self.is_main && !is_dyn_import {
- Permissions::allow_all()
- } else {
- state.borrow::<Permissions>().clone()
- };
+ // The permissions that should be applied to any dynamically imported module
+ let dynamic_permissions = state.borrow::<Permissions>().clone();
let program_state = state.borrow::<Arc<ProgramState>>().clone();
drop(state);
- // TODO(bartlomieju): I'm not sure if it's correct to ignore
- // bad referrer - this is the case for `Deno.core.evalContext()` where
- // `ref_str` is `<unknown>`.
- let maybe_referrer = if let Some(ref_str) = maybe_referrer {
- ModuleSpecifier::resolve_url(&ref_str).ok()
- } else {
- None
- };
-
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param
async move {
program_state
.prepare_module_load(
- module_specifier,
- maybe_referrer,
+ specifier,
target_lib,
- permissions,
- is_dyn_import,
+ dynamic_permissions,
+ is_dynamic,
maybe_import_map,
)
.await
diff --git a/cli/ops/errors.rs b/cli/ops/errors.rs
index 04281e383..dbb72139d 100644
--- a/cli/ops/errors.rs
+++ b/cli/ops/errors.rs
@@ -39,7 +39,7 @@ fn op_apply_source_map(
args.line_number.into(),
args.column_number.into(),
&mut mappings_map,
- &super::program_state(state).ts_compiler,
+ super::program_state(state),
);
Ok(json!({
diff --git a/cli/program_state.rs b/cli/program_state.rs
index b921b6807..6e3a51663 100644
--- a/cli/program_state.rs
+++ b/cli/program_state.rs
@@ -8,16 +8,20 @@ use crate::import_map::ImportMap;
use crate::inspector::InspectorServer;
use crate::lockfile::Lockfile;
use crate::media_type::MediaType;
-use crate::module_graph::ModuleGraphFile;
-use crate::module_graph::ModuleGraphLoader;
+use crate::module_graph2::CheckOptions;
use crate::module_graph2::GraphBuilder2;
use crate::module_graph2::TranspileOptions;
+use crate::module_graph2::TypeLib;
use crate::permissions::Permissions;
+use crate::source_maps::SourceMapGetter;
use crate::specifier_handler::FetchHandler;
use crate::tsc::CompiledModule;
use crate::tsc::TargetLib;
use crate::tsc::TsCompiler;
+
+use deno_core::error::generic_error;
use deno_core::error::AnyError;
+use deno_core::url::Url;
use deno_core::ModuleSpecifier;
use std::cell::RefCell;
use std::env;
@@ -115,89 +119,66 @@ impl ProgramState {
/// and traspilation.
pub async fn prepare_module_load(
self: &Arc<Self>,
- module_specifier: ModuleSpecifier,
- maybe_referrer: Option<ModuleSpecifier>,
+ specifier: ModuleSpecifier,
target_lib: TargetLib,
- permissions: Permissions,
- is_dyn_import: bool,
+ dynamic_permissions: Permissions,
+ is_dynamic: bool,
maybe_import_map: Option<ImportMap>,
) -> Result<(), AnyError> {
- let module_specifier = module_specifier.clone();
+ let specifier = specifier.clone();
+ let handler =
+ Rc::new(RefCell::new(FetchHandler::new(self, dynamic_permissions)?));
+ let mut builder = GraphBuilder2::new(handler, maybe_import_map);
+ builder.add(&specifier, is_dynamic).await?;
+ let mut graph = builder.get_graph(&self.lockfile);
+ let debug = self.flags.log_level == Some(log::Level::Debug);
+ let maybe_config_path = self.flags.config_path.clone();
if self.flags.no_check {
- debug!("Transpiling root: {}", module_specifier);
- // TODO(kitsonk) note that self.permissions != permissions, which is
- // something that should be handled better in the future.
- let handler =
- Rc::new(RefCell::new(FetchHandler::new(self, permissions.clone())?));
- let mut builder = GraphBuilder2::new(handler, maybe_import_map);
- builder.insert(&module_specifier).await?;
- let mut graph = builder.get_graph(&self.lockfile)?;
-
let (stats, maybe_ignored_options) =
graph.transpile(TranspileOptions {
- debug: self.flags.log_level == Some(log::Level::Debug),
- maybe_config_path: self.flags.config_path.clone(),
+ debug,
+ maybe_config_path,
+ reload: self.flags.reload,
})?;
-
+ debug!("{}", stats);
if let Some(ignored_options) = maybe_ignored_options {
eprintln!("{}", ignored_options);
}
-
- debug!("{}", stats);
} else {
- let mut module_graph_loader = ModuleGraphLoader::new(
- self.file_fetcher.clone(),
- maybe_import_map,
- permissions.clone(),
- is_dyn_import,
- false,
- );
- module_graph_loader
- .add_to_graph(&module_specifier, maybe_referrer)
- .await?;
- let module_graph = module_graph_loader.get_graph();
-
- let out = self
- .file_fetcher
- .fetch_cached_source_file(&module_specifier, permissions.clone())
- .expect("Source file not found");
-
- let module_graph_files = module_graph.values().collect::<Vec<_>>();
- // Check integrity of every file in module graph
- if let Some(ref lockfile) = self.lockfile {
- let mut g = lockfile.lock().unwrap();
-
- for graph_file in &module_graph_files {
- let check_passed =
- g.check_or_insert(&graph_file.url, &graph_file.source_code);
-
- if !check_passed {
- eprintln!(
- "Subresource integrity check failed --lock={}\n{}",
- g.filename.display(),
- graph_file.url
- );
- std::process::exit(10);
+ let lib = match target_lib {
+ TargetLib::Main => {
+ if self.flags.unstable {
+ TypeLib::UnstableDenoWindow
+ } else {
+ TypeLib::DenoWindow
}
}
- }
-
- // Check if we need to compile files.
- let should_compile = needs_compilation(
- self.ts_compiler.compile_js,
- out.media_type,
- &module_graph_files,
- );
- let allow_js = should_allow_js(&module_graph_files);
+ TargetLib::Worker => {
+ if self.flags.unstable {
+ TypeLib::UnstableDenoWorker
+ } else {
+ TypeLib::DenoWorker
+ }
+ }
+ };
+ let (stats, diagnostics, maybe_ignored_options) =
+ graph.check(CheckOptions {
+ debug,
+ emit: true,
+ lib,
+ maybe_config_path,
+ reload: self.flags.reload,
+ })?;
- if should_compile {
- self
- .ts_compiler
- .compile(self, &out, target_lib, &module_graph, allow_js)
- .await?;
+ debug!("{}", stats);
+ if let Some(ignored_options) = maybe_ignored_options {
+ eprintln!("{}", ignored_options);
}
- }
+ if !diagnostics.0.is_empty() {
+ return Err(generic_error(diagnostics.to_string()));
+ }
+ };
if let Some(ref lockfile) = self.lockfile {
let g = lockfile.lock().unwrap();
@@ -207,44 +188,39 @@ impl ProgramState {
Ok(())
}
- // TODO(bartlomieju): this method doesn't need to be async anymore
- /// This method is used after `prepare_module_load` finishes and JsRuntime
- /// starts loading source and executing source code. This method shouldn't
- /// perform any IO (besides $DENO_DIR) and only operate on sources collected
- /// during `prepare_module_load`.
- pub async fn fetch_compiled_module(
+ pub fn fetch_compiled_module(
&self,
module_specifier: ModuleSpecifier,
- _maybe_referrer: Option<ModuleSpecifier>,
+ maybe_referrer: Option<ModuleSpecifier>,
) -> Result<CompiledModule, AnyError> {
let out = self
.file_fetcher
.fetch_cached_source_file(&module_specifier, Permissions::allow_all())
.expect("Cached source file doesn't exist");
- // Check if we need to compile files
- let was_compiled = match out.media_type {
- MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true,
- MediaType::JavaScript => self.ts_compiler.compile_js,
- _ => false,
- };
-
- let compiled_module = if was_compiled {
- match self.ts_compiler.get_compiled_module(&out.url) {
- Ok(module) => module,
- Err(e) => {
- let msg = format!(
- "Failed to get compiled source code of \"{}\".\nReason: {}\n\
- If the source file provides only type exports, prefer to use \"import type\" or \"export type\" syntax instead.",
- out.url, e.to_string()
- );
- info!("{} {}", crate::colors::yellow("Warning"), msg);
-
- CompiledModule {
- code: "".to_string(),
- name: out.url.to_string(),
- }
- }
+ let url = out.url.clone();
+ let compiled_module = if let Some((code, _)) = self.get_emit(&url) {
+ CompiledModule {
+ code: String::from_utf8(code).unwrap(),
+ name: out.url.to_string(),
+ }
+ // We expect a compiled source for any non-JavaScript files, except for
+ // local files that have an unknown media type and no referrer (root modules
+ // that do not have an extension.)
+ } else if out.media_type != MediaType::JavaScript
+ && !(out.media_type == MediaType::Unknown
+ && maybe_referrer.is_none()
+ && url.scheme() == "file")
+ {
+ let message = if let Some(referrer) = maybe_referrer {
+ format!("Compiled module not found \"{}\"\n From: {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", module_specifier, referrer)
+ } else {
+ format!("Compiled module not found \"{}\"\n If the source module contains only types, use `import type` and `export type` to import it instead.", module_specifier)
+ };
+ info!("{}: {}", crate::colors::yellow("warning"), message);
+ CompiledModule {
+ code: "".to_string(),
+ name: out.url.to_string(),
}
} else {
CompiledModule {
@@ -256,6 +232,37 @@ impl ProgramState {
Ok(compiled_module)
}
+ // TODO(@kitsonk) this should be a straight forward API on file_fetcher or
+ // whatever future refactors do...
+ fn get_emit(&self, url: &Url) -> Option<(Vec<u8>, Option<Vec<u8>>)> {
+ match url.scheme() {
+ // we should only be looking for emits for schemes that denote external
+ // modules, which the disk_cache supports
+ "wasm" | "file" | "http" | "https" => (),
+ _ => {
+ return None;
+ }
+ }
+ let emit_path = self
+ .dir
+ .gen_cache
+ .get_cache_filename_with_extension(&url, "js");
+ let emit_map_path = self
+ .dir
+ .gen_cache
+ .get_cache_filename_with_extension(&url, "js.map");
+ if let Ok(code) = self.dir.gen_cache.get(&emit_path) {
+ let maybe_map = if let Ok(map) = self.dir.gen_cache.get(&emit_map_path) {
+ Some(map)
+ } else {
+ None
+ };
+ Some((code, maybe_map))
+ } else {
+ None
+ }
+ }
+
/// Quits the process if the --unstable flag was not provided.
///
/// This is intentionally a non-recoverable check so that people cannot probe
@@ -279,57 +286,62 @@ impl ProgramState {
}
}
-/// Determine if TS compiler should be run with `allowJs` setting on. This
-/// is the case when there's either:
-/// - a JavaScript file with non-JavaScript import
-/// - JSX import
-fn should_allow_js(module_graph_files: &[&ModuleGraphFile]) -> bool {
- module_graph_files.iter().any(|module_file| {
- if module_file.media_type == MediaType::JSX {
- true
- } else if module_file.media_type == MediaType::JavaScript {
- module_file.imports.iter().any(|import_desc| {
- let import_file = module_graph_files
- .iter()
- .find(|f| {
- f.specifier == import_desc.resolved_specifier.to_string().as_str()
- })
- .expect("Failed to find imported file");
- let media_type = import_file.media_type;
- media_type == MediaType::TypeScript
- || media_type == MediaType::TSX
- || media_type == MediaType::JSX
- })
+// TODO(@kitsonk) this is only temporary, but should be refactored to somewhere
+// else, like a refactored file_fetcher.
+impl SourceMapGetter for ProgramState {
+ fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
+ if let Ok(specifier) = ModuleSpecifier::resolve_url(file_name) {
+ if let Some((code, maybe_map)) = self.get_emit(&specifier.as_url()) {
+ if maybe_map.is_some() {
+ maybe_map
+ } else {
+ let code = String::from_utf8(code).unwrap();
+ let lines: Vec<&str> = code.split('\n').collect();
+ if let Some(last_line) = lines.last() {
+ if last_line
+ .starts_with("//# sourceMappingURL=data:application/json;base64,")
+ {
+ let input = last_line.trim_start_matches(
+ "//# sourceMappingURL=data:application/json;base64,",
+ );
+ let decoded_map = base64::decode(input)
+ .expect("Unable to decode source map from emitted file.");
+ Some(decoded_map)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ } else {
+ None
+ }
} else {
- false
+ None
}
- })
-}
-
-// Compilation happens if either:
-// - `checkJs` is set to true in TS config
-// - entry point is a TS file
-// - any dependency in module graph is a TS file
-fn needs_compilation(
- compile_js: bool,
- media_type: MediaType,
- module_graph_files: &[&ModuleGraphFile],
-) -> bool {
- let mut needs_compilation = match media_type {
- MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true,
- MediaType::JavaScript => compile_js,
- _ => false,
- };
-
- needs_compilation |= module_graph_files.iter().any(|module_file| {
- let media_type = module_file.media_type;
-
- media_type == (MediaType::TypeScript)
- || media_type == (MediaType::TSX)
- || media_type == (MediaType::JSX)
- });
+ }
- needs_compilation
+ fn get_source_line(
+ &self,
+ file_name: &str,
+ line_number: usize,
+ ) -> Option<String> {
+ if let Ok(specifier) = ModuleSpecifier::resolve_url(file_name) {
+ self
+ .file_fetcher
+ .fetch_cached_source_file(&specifier, Permissions::allow_all())
+ .map(|out| {
+ // Do NOT use .lines(): it skips the terminating empty line.
+ // (due to internally using .split_terminator() instead of .split())
+ let lines: Vec<&str> = out.source_code.split('\n').collect();
+ assert!(lines.len() > line_number);
+ lines[line_number].to_string()
+ })
+ } else {
+ None
+ }
+ }
}
#[test]
@@ -337,203 +349,3 @@ fn thread_safe() {
fn f<S: Send + Sync>(_: S) {}
f(ProgramState::mock(vec![], None));
}
-
-#[test]
-fn test_should_allow_js() {
- use crate::ast::Location;
- use crate::module_graph::ImportDescriptor;
-
- assert!(should_allow_js(&[
- &ModuleGraphFile {
- specifier: "file:///some/file.ts".to_string(),
- url: "file:///some/file.ts".to_string(),
- redirect: None,
- filename: "some/file.ts".to_string(),
- imports: vec![],
- version_hash: "1".to_string(),
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- media_type: MediaType::TypeScript,
- source_code: "function foo() {}".to_string(),
- },
- &ModuleGraphFile {
- specifier: "file:///some/file1.js".to_string(),
- url: "file:///some/file1.js".to_string(),
- redirect: None,
- filename: "some/file1.js".to_string(),
- version_hash: "1".to_string(),
- imports: vec![ImportDescriptor {
- specifier: "./file.ts".to_string(),
- resolved_specifier: ModuleSpecifier::resolve_url(
- "file:///some/file.ts",
- )
- .unwrap(),
- type_directive: None,
- resolved_type_directive: None,
- location: Location {
- filename: "file:///some/file1.js".to_string(),
- line: 0,
- col: 0,
- },
- }],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- media_type: MediaType::JavaScript,
- source_code: "function foo() {}".to_string(),
- },
- ],));
-
- assert!(should_allow_js(&[
- &ModuleGraphFile {
- specifier: "file:///some/file.jsx".to_string(),
- url: "file:///some/file.jsx".to_string(),
- redirect: None,
- filename: "some/file.jsx".to_string(),
- imports: vec![],
- version_hash: "1".to_string(),
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- media_type: MediaType::JSX,
- source_code: "function foo() {}".to_string(),
- },
- &ModuleGraphFile {
- specifier: "file:///some/file.ts".to_string(),
- url: "file:///some/file.ts".to_string(),
- redirect: None,
- filename: "some/file.ts".to_string(),
- version_hash: "1".to_string(),
- imports: vec![ImportDescriptor {
- specifier: "./file.jsx".to_string(),
- resolved_specifier: ModuleSpecifier::resolve_url(
- "file:///some/file.jsx",
- )
- .unwrap(),
- type_directive: None,
- resolved_type_directive: None,
- location: Location {
- filename: "file:///some/file1.ts".to_string(),
- line: 0,
- col: 0,
- },
- }],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- media_type: MediaType::TypeScript,
- source_code: "function foo() {}".to_string(),
- },
- ]));
-
- assert!(!should_allow_js(&[
- &ModuleGraphFile {
- specifier: "file:///some/file.js".to_string(),
- url: "file:///some/file.js".to_string(),
- redirect: None,
- filename: "some/file.js".to_string(),
- imports: vec![],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- version_hash: "1".to_string(),
- type_headers: vec![],
- media_type: MediaType::JavaScript,
- source_code: "function foo() {}".to_string(),
- },
- &ModuleGraphFile {
- specifier: "file:///some/file1.js".to_string(),
- url: "file:///some/file1.js".to_string(),
- redirect: None,
- filename: "some/file1.js".to_string(),
- imports: vec![ImportDescriptor {
- specifier: "./file.js".to_string(),
- resolved_specifier: ModuleSpecifier::resolve_url(
- "file:///some/file.js",
- )
- .unwrap(),
- type_directive: None,
- resolved_type_directive: None,
- location: Location {
- filename: "file:///some/file.js".to_string(),
- line: 0,
- col: 0,
- },
- }],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- version_hash: "1".to_string(),
- type_headers: vec![],
- media_type: MediaType::JavaScript,
- source_code: "function foo() {}".to_string(),
- },
- ],));
-}
-
-#[test]
-fn test_needs_compilation() {
- assert!(!needs_compilation(
- false,
- MediaType::JavaScript,
- &[&ModuleGraphFile {
- specifier: "some/file.js".to_string(),
- url: "file:///some/file.js".to_string(),
- redirect: None,
- filename: "some/file.js".to_string(),
- imports: vec![],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- version_hash: "1".to_string(),
- media_type: MediaType::JavaScript,
- source_code: "function foo() {}".to_string(),
- }],
- ));
-
- assert!(!needs_compilation(false, MediaType::JavaScript, &[]));
- assert!(needs_compilation(true, MediaType::JavaScript, &[]));
- assert!(needs_compilation(false, MediaType::TypeScript, &[]));
- assert!(needs_compilation(false, MediaType::JSX, &[]));
- assert!(needs_compilation(false, MediaType::TSX, &[]));
- assert!(needs_compilation(
- false,
- MediaType::JavaScript,
- &[
- &ModuleGraphFile {
- specifier: "file:///some/file.ts".to_string(),
- url: "file:///some/file.ts".to_string(),
- redirect: None,
- filename: "some/file.ts".to_string(),
- imports: vec![],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- media_type: MediaType::TypeScript,
- version_hash: "1".to_string(),
- source_code: "function foo() {}".to_string(),
- },
- &ModuleGraphFile {
- specifier: "file:///some/file1.js".to_string(),
- url: "file:///some/file1.js".to_string(),
- redirect: None,
- filename: "some/file1.js".to_string(),
- imports: vec![],
- referenced_files: vec![],
- lib_directives: vec![],
- types_directives: vec![],
- type_headers: vec![],
- version_hash: "1".to_string(),
- media_type: MediaType::JavaScript,
- source_code: "function foo() {}".to_string(),
- },
- ],
- ));
-}
diff --git a/cli/source_maps.rs b/cli/source_maps.rs
index 4744482a7..f31228bdc 100644
--- a/cli/source_maps.rs
+++ b/cli/source_maps.rs
@@ -6,8 +6,9 @@ use deno_core::error::JsError as CoreJsError;
use sourcemap::SourceMap;
use std::collections::HashMap;
use std::str;
+use std::sync::Arc;
-pub trait SourceMapGetter {
+pub trait SourceMapGetter: Sync + Send {
/// Returns the raw source map file.
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>>;
fn get_source_line(
@@ -26,7 +27,7 @@ pub type CachedMaps = HashMap<String, Option<SourceMap>>;
/// source, rather than the transpiled source code.
pub fn apply_source_map<G: SourceMapGetter>(
js_error: &CoreJsError,
- getter: &G,
+ getter: Arc<G>,
) -> CoreJsError {
// Note that js_error.frames has already been source mapped in
// prepareStackTrace().
@@ -39,7 +40,7 @@ pub fn apply_source_map<G: SourceMapGetter>(
// start_column is 0-based, we need 1-based here.
js_error.start_column.map(|n| n + 1),
&mut mappings_map,
- getter,
+ getter.clone(),
);
let start_column = start_column.map(|n| n - 1);
// It is better to just move end_column to be the same distance away from
@@ -87,7 +88,7 @@ fn get_maybe_orig_position<G: SourceMapGetter>(
line_number: Option<i64>,
column_number: Option<i64>,
mappings_map: &mut CachedMaps,
- getter: &G,
+ getter: Arc<G>,
) -> (Option<String>, Option<i64>, Option<i64>) {
match (file_name, line_number, column_number) {
(Some(file_name_v), Some(line_v), Some(column_v)) => {
@@ -104,7 +105,7 @@ pub fn get_orig_position<G: SourceMapGetter>(
line_number: i64,
column_number: i64,
mappings_map: &mut CachedMaps,
- getter: &G,
+ getter: Arc<G>,
) -> (String, i64, i64) {
let maybe_source_map = get_mappings(&file_name, mappings_map, getter);
let default_pos = (file_name, line_number, column_number);
@@ -134,7 +135,7 @@ pub fn get_orig_position<G: SourceMapGetter>(
fn get_mappings<'a, G: SourceMapGetter>(
file_name: &str,
mappings_map: &'a mut CachedMaps,
- getter: &G,
+ getter: Arc<G>,
) -> &'a Option<SourceMap> {
mappings_map
.entry(file_name.to_string())
@@ -145,7 +146,7 @@ fn get_mappings<'a, G: SourceMapGetter>(
// the module meta data.
fn parse_map_string<G: SourceMapGetter>(
file_name: &str,
- getter: &G,
+ getter: Arc<G>,
) -> Option<SourceMap> {
getter
.get_source_map(file_name)
@@ -207,8 +208,8 @@ mod tests {
frames: vec![],
stack: None,
};
- let getter = MockSourceMapGetter {};
- let actual = apply_source_map(&e, &getter);
+ let getter = Arc::new(MockSourceMapGetter {});
+ let actual = apply_source_map(&e, getter);
assert_eq!(actual.source_line, Some("console.log('foo');".to_string()));
}
}
diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs
index 5d9c19a5e..016ad0468 100644
--- a/cli/specifier_handler.rs
+++ b/cli/specifier_handler.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use crate::ast::Location;
use crate::deno_dir::DenoDir;
use crate::disk_cache::DiskCache;
use crate::file_fetcher::SourceFileFetcher;
@@ -25,8 +26,29 @@ pub type DependencyMap = HashMap<String, Dependency>;
pub type FetchFuture =
Pin<Box<(dyn Future<Output = Result<CachedModule, AnyError>> + 'static)>>;
+/// 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>)>,
@@ -44,6 +66,7 @@ impl Default for CachedModule {
fn default() -> Self {
let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap();
CachedModule {
+ is_remote: false,
maybe_dependencies: None,
maybe_emit: None,
maybe_emit_path: None,
@@ -76,8 +99,12 @@ impl Default for Emit {
}
}
-#[derive(Debug, Clone, Default)]
+#[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>,
@@ -86,17 +113,33 @@ pub struct Dependency {
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 {
/// Instructs the handler to fetch a specifier or retrieve its value from the
/// cache.
- fn fetch(&mut self, specifier: ModuleSpecifier) -> FetchFuture;
+ 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_ts_build_info(
+ fn get_tsbuildinfo(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError>;
@@ -117,10 +160,10 @@ pub trait SpecifierHandler {
) -> Result<(), AnyError>;
/// Set the build info for a module specifier, also providing the cache type.
- fn set_ts_build_info(
+ fn set_tsbuildinfo(
&mut self,
specifier: &ModuleSpecifier,
- ts_build_info: String,
+ tsbuildinfo: String,
) -> Result<(), AnyError>;
/// Set the graph dependencies for a given module specifier.
@@ -170,15 +213,18 @@ impl CompiledFileMetadata {
/// 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,
+ /// A set of permissions to apply to dynamic imports.
+ dynamic_permissions: Permissions,
+ /// A clone of the `program_state` file fetcher.
file_fetcher: SourceFileFetcher,
- permissions: Permissions,
}
impl FetchHandler {
pub fn new(
program_state: &Arc<ProgramState>,
- permissions: Permissions,
+ dynamic_permissions: Permissions,
) -> Result<Self, AnyError> {
let custom_root = env::var("DENO_DIR").map(String::into).ok();
let deno_dir = DenoDir::new(custom_root)?;
@@ -187,23 +233,54 @@ impl FetchHandler {
Ok(FetchHandler {
disk_cache,
+ dynamic_permissions,
file_fetcher,
- permissions,
})
}
}
impl SpecifierHandler for FetchHandler {
- fn fetch(&mut self, requested_specifier: ModuleSpecifier) -> FetchFuture {
- let permissions = self.permissions.clone();
+ 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 permissions = if is_dynamic {
+ self.dynamic_permissions.clone()
+ } else {
+ Permissions::allow_all()
+ };
let file_fetcher = self.file_fetcher.clone();
let disk_cache = self.disk_cache.clone();
+ let maybe_referrer: Option<ModuleSpecifier> =
+ if let Some(location) = &maybe_location {
+ Some(location.clone().into())
+ } else {
+ None
+ };
async move {
let source_file = file_fetcher
- .fetch_source_file(&requested_specifier, None, permissions)
- .await?;
+ .fetch_source_file(&requested_specifier, maybe_referrer, permissions)
+ .await
+ .map_err(|err| {
+ if let Some(location) = maybe_location {
+ if !is_dynamic {
+ HandlerError::FetchErrorWithLocation(err.to_string(), location)
+ .into()
+ } else {
+ err
+ }
+ } else {
+ err
+ }
+ })?;
let url = source_file.url.clone();
+ let is_remote = url.scheme() != "file";
let filename = disk_cache.get_cache_filename_with_extension(&url, "meta");
let maybe_version = if let Ok(bytes) = disk_cache.get(&filename) {
if let Ok(compiled_file_metadata) =
@@ -237,6 +314,7 @@ impl SpecifierHandler for FetchHandler {
let specifier = ModuleSpecifier::from(url);
Ok(CachedModule {
+ is_remote,
maybe_dependencies: None,
maybe_emit,
maybe_emit_path,
@@ -252,31 +330,32 @@ impl SpecifierHandler for FetchHandler {
.boxed_local()
}
- fn get_ts_build_info(
+ fn get_tsbuildinfo(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier.as_url(), "buildinfo");
- if let Ok(ts_build_info) = self.disk_cache.get(&filename) {
- return Ok(Some(String::from_utf8(ts_build_info)?));
+ if let Ok(tsbuildinfo) = self.disk_cache.get(&filename) {
+ Ok(Some(String::from_utf8(tsbuildinfo)?))
+ } else {
+ Ok(None)
}
-
- Ok(None)
}
- fn set_ts_build_info(
+ fn set_tsbuildinfo(
&mut self,
specifier: &ModuleSpecifier,
- ts_build_info: String,
+ tsbuildinfo: String,
) -> Result<(), AnyError> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier.as_url(), "buildinfo");
+ debug!("set_tsbuildinfo - filename {:?}", filename);
self
.disk_cache
- .set(&filename, ts_build_info.as_bytes())
+ .set(&filename, tsbuildinfo.as_bytes())
.map_err(|e| e.into())
}
@@ -366,8 +445,8 @@ pub mod tests {
let fetch_handler = FetchHandler {
disk_cache,
+ dynamic_permissions: Permissions::default(),
file_fetcher,
- permissions: Permissions::allow_all(),
};
(temp_dir, fetch_handler)
@@ -381,8 +460,10 @@ pub mod tests {
"http://localhost:4545/cli/tests/subdir/mod2.ts",
)
.unwrap();
- let cached_module: CachedModule =
- file_fetcher.fetch(specifier.clone()).await.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);
@@ -401,18 +482,43 @@ pub mod tests {
"http://localhost:4545/cli/tests/subdir/mod2.ts",
)
.unwrap();
- let cached_module: CachedModule =
- file_fetcher.fetch(specifier.clone()).await.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()).await.unwrap();
+ 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 = ModuleSpecifier::resolve_url_or_path(
+ "http://localhost:4545/cli/tests/subdir/mod2.ts",
+ )
+ .unwrap();
+ let cached_module: CachedModule =
+ file_fetcher.fetch(specifier, None, false).await.unwrap();
+ assert_eq!(cached_module.is_remote, true);
+ let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
+ let specifier = ModuleSpecifier::resolve_url_or_path(
+ c.join("tests/subdir/mod1.ts").as_os_str().to_str().unwrap(),
+ )
+ .unwrap();
+ let cached_module: CachedModule =
+ file_fetcher.fetch(specifier, None, false).await.unwrap();
+ assert_eq!(cached_module.is_remote, false);
+ }
}
diff --git a/cli/tests/020_json_modules.ts.out b/cli/tests/020_json_modules.ts.out
index 8c07af203..a89bd2ad6 100644
--- a/cli/tests/020_json_modules.ts.out
+++ b/cli/tests/020_json_modules.ts.out
@@ -1,3 +1,5 @@
[WILDCARD]
-error: TypeError: Cannot resolve extension for "[WILDCARD]config.json" with mediaType "Json".
+error: An unsupported media type was attempted to be imported as a module.
+ Specifier: [WILDCARD]cli/tests/subdir/config.json
+ MediaType: Json
[WILDCARD] \ No newline at end of file
diff --git a/cli/tests/023_no_ext b/cli/tests/023_no_ext
new file mode 100644
index 000000000..0dcfb6209
--- /dev/null
+++ b/cli/tests/023_no_ext
@@ -0,0 +1,2 @@
+import * as mod4 from "./subdir/mod4.js";
+console.log(mod4.isMod4);
diff --git a/cli/tests/023_no_ext.out b/cli/tests/023_no_ext.out
new file mode 100644
index 000000000..27ba77dda
--- /dev/null
+++ b/cli/tests/023_no_ext.out
@@ -0,0 +1 @@
+true
diff --git a/cli/tests/023_no_ext_with_headers b/cli/tests/023_no_ext_with_headers
deleted file mode 100644
index 87951d835..000000000
--- a/cli/tests/023_no_ext_with_headers
+++ /dev/null
@@ -1 +0,0 @@
-console.log("HELLO");
diff --git a/cli/tests/023_no_ext_with_headers.out b/cli/tests/023_no_ext_with_headers.out
deleted file mode 100644
index e427984d4..000000000
--- a/cli/tests/023_no_ext_with_headers.out
+++ /dev/null
@@ -1 +0,0 @@
-HELLO
diff --git a/cli/tests/bundle/fixture14.out b/cli/tests/bundle/fixture14.out
index 06e93a7cc..c1a14cebe 100644
--- a/cli/tests/bundle/fixture14.out
+++ b/cli/tests/bundle/fixture14.out
@@ -12,12 +12,12 @@ const lib = function() {
};
}();
const c = function() {
- const c1;
+ const c1 = [];
return {
c: c1
};
}();
- const mod;
+ const mod = [];
return {
mod
};
diff --git a/cli/tests/config.ts b/cli/tests/config.ts
index e08061e77..6b1cb7322 100644
--- a/cli/tests/config.ts
+++ b/cli/tests/config.ts
@@ -1,5 +1,17 @@
-const map = new Map<string, { foo: string }>();
+/* eslint-disable */
+function b() {
+ return function (
+ _target: any,
+ _propertyKey: string,
+ _descriptor: PropertyDescriptor,
+ ) {
+ console.log("b");
+ };
+}
-if (map.get("bar").foo) {
- console.log("here");
+class A {
+ @b()
+ a() {
+ console.log("a");
+ }
}
diff --git a/cli/tests/config.ts.out b/cli/tests/config.ts.out
index 9840dba2e..99b4a7ea4 100644
--- a/cli/tests/config.ts.out
+++ b/cli/tests/config.ts.out
@@ -1,7 +1,7 @@
[WILDCARD]Unsupported compiler options in "[WILDCARD]config.tsconfig.json".
The following options were ignored:
module, target
-error: TS2532 [ERROR]: Object is possibly 'undefined'.
-if (map.get("bar").foo) {
- ~~~~~~~~~~~~~~
- at [WILDCARD]tests/config.ts:3:5
+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() {
+ ^
+ at file:///[WILDCARD]cli/tests/config.ts:[WILDCARD]
diff --git a/cli/tests/config.tsconfig.json b/cli/tests/config.tsconfig.json
index 074d7ac0b..dcabb50a4 100644
--- a/cli/tests/config.tsconfig.json
+++ b/cli/tests/config.tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
+ "experimentalDecorators": false,
"module": "amd",
- "strict": true,
"target": "es5"
}
}
diff --git a/cli/tests/disallow_http_from_https_js.out b/cli/tests/disallow_http_from_https_js.out
index e4e421159..405859e4d 100644
--- a/cli/tests/disallow_http_from_https_js.out
+++ b/cli/tests/disallow_http_from_https_js.out
@@ -1,2 +1,3 @@
-error: Modules loaded over https:// are not allowed to import modules over http://
-Imported from "https://localhost:5545/cli/tests/disallow_http_from_https.js:2"
+error: Modules imported via https are not allowed to import http modules.
+ Importing: http://localhost:4545/cli/tests/001_hello.js
+ at https://localhost:5545/cli/tests/disallow_http_from_https.js:2:0
diff --git a/cli/tests/disallow_http_from_https_ts.out b/cli/tests/disallow_http_from_https_ts.out
index 55e10b733..b63ba0c67 100644
--- a/cli/tests/disallow_http_from_https_ts.out
+++ b/cli/tests/disallow_http_from_https_ts.out
@@ -1,2 +1,3 @@
-error: Modules loaded over https:// are not allowed to import modules over http://
-Imported from "https://localhost:5545/cli/tests/disallow_http_from_https.ts:2"
+error: Modules imported via https are not allowed to import http modules.
+ Importing: http://localhost:4545/cli/tests/001_hello.js
+ at https://localhost:5545/cli/tests/disallow_http_from_https.ts:2:0
diff --git a/cli/tests/error_004_missing_module.ts.out b/cli/tests/error_004_missing_module.ts.out
index 121555868..68032afb4 100644
--- a/cli/tests/error_004_missing_module.ts.out
+++ b/cli/tests/error_004_missing_module.ts.out
@@ -1,2 +1,2 @@
-[WILDCARD]error: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_004_missing_module.ts"
-Imported from "[WILDCARD]/error_004_missing_module.ts:2"
+[WILDCARD]error: Cannot resolve module "file:///[WILDCARD]cli/tests/bad-module.ts" from "file:///[WILDCARD]cli/tests/error_004_missing_module.ts"
+ at file:///[WILDCARD]cli/tests/error_004_missing_module.ts:2:0
diff --git a/cli/tests/error_005_missing_dynamic_import.ts.out b/cli/tests/error_005_missing_dynamic_import.ts.out
index 8a64175ec..346e8cd6f 100644
--- a/cli/tests/error_005_missing_dynamic_import.ts.out
+++ b/cli/tests/error_005_missing_dynamic_import.ts.out
@@ -1 +1 @@
-error: Uncaught TypeError: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_005_missing_dynamic_import.ts"
+error: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_005_missing_dynamic_import.ts"
diff --git a/cli/tests/error_006_import_ext_failure.ts.out b/cli/tests/error_006_import_ext_failure.ts.out
index 9e1c99970..01f8af07e 100644
--- a/cli/tests/error_006_import_ext_failure.ts.out
+++ b/cli/tests/error_006_import_ext_failure.ts.out
@@ -1,2 +1,2 @@
[WILDCARD]error: Cannot resolve module "[WILDCARD]/non-existent" from "[WILDCARD]/error_006_import_ext_failure.ts"
-Imported from "[WILDCARD]/error_006_import_ext_failure.ts:1"
+ at file:///[WILDCARD]cli/tests/error_006_import_ext_failure.ts:1:0
diff --git a/cli/tests/error_011_bad_module_specifier.ts b/cli/tests/error_011_bad_module_specifier.ts
index e74d6b821..a9ccc4523 100644
--- a/cli/tests/error_011_bad_module_specifier.ts
+++ b/cli/tests/error_011_bad_module_specifier.ts
@@ -1,2 +1,4 @@
// eslint-disable-next-line
import * as badModule from "bad-module.ts";
+
+console.log(badModule);
diff --git a/cli/tests/error_015_dynamic_import_permissions.out b/cli/tests/error_015_dynamic_import_permissions.out
index 7078ac61c..9b47ebe9d 100644
--- a/cli/tests/error_015_dynamic_import_permissions.out
+++ b/cli/tests/error_015_dynamic_import_permissions.out
@@ -1 +1 @@
-error: Uncaught TypeError: network access to "http://localhost:4545/cli/tests/subdir/mod4.js", run again with the --allow-net flag
+error: network access to "http://localhost:4545/cli/tests/subdir/mod4.js", run again with the --allow-net flag
diff --git a/cli/tests/error_016_dynamic_import_permissions2.out b/cli/tests/error_016_dynamic_import_permissions2.out
index 2babfbf9f..f54b4a7fe 100644
--- a/cli/tests/error_016_dynamic_import_permissions2.out
+++ b/cli/tests/error_016_dynamic_import_permissions2.out
@@ -1,3 +1,4 @@
[WILDCARD]
-error: Uncaught TypeError: read access to "[WILDCARD]passwd", run again with the --allow-read flag
-Imported from "[WILDCARD]evil_remote_import.js:3"
+error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
+ Importing: file:///c:/etc/passwd
+ at http://localhost:4545/cli/tests/subdir/evil_remote_import.js:3:0
diff --git a/cli/tests/error_local_static_import_from_remote.js.out b/cli/tests/error_local_static_import_from_remote.js.out
index 1a2dcb2e3..071ca36d4 100644
--- a/cli/tests/error_local_static_import_from_remote.js.out
+++ b/cli/tests/error_local_static_import_from_remote.js.out
@@ -1,3 +1,4 @@
[WILDCARD]
-error: Remote modules are not allowed to statically import local modules. Use dynamic import instead.
-Imported from "[WILDCARD]error_local_static_import_from_remote.js:1"
+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/cli/tests/error_local_static_import_from_remote.js:1:0
diff --git a/cli/tests/error_local_static_import_from_remote.ts.out b/cli/tests/error_local_static_import_from_remote.ts.out
index a2f2e1bbf..38f4c02bc 100644
--- a/cli/tests/error_local_static_import_from_remote.ts.out
+++ b/cli/tests/error_local_static_import_from_remote.ts.out
@@ -1,3 +1,4 @@
[WILDCARD]
-error: Remote modules are not allowed to statically import local modules. Use dynamic import instead.
-Imported from "[WILDCARD]error_local_static_import_from_remote.ts:1"
+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/cli/tests/error_local_static_import_from_remote.ts:1:0
diff --git a/cli/tests/fix_exotic_specifiers.ts b/cli/tests/fix_exotic_specifiers.ts
new file mode 100644
index 000000000..101667b2a
--- /dev/null
+++ b/cli/tests/fix_exotic_specifiers.ts
@@ -0,0 +1,3 @@
+import clone from "https://jspm.dev/lodash@4/clone";
+
+console.log(clone);
diff --git a/cli/tests/fix_exotic_specifiers.ts.out b/cli/tests/fix_exotic_specifiers.ts.out
new file mode 100644
index 000000000..7afdb808d
--- /dev/null
+++ b/cli/tests/fix_exotic_specifiers.ts.out
@@ -0,0 +1 @@
+[Function: clone]
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 047440a70..b9264aa0f 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -1807,9 +1807,9 @@ itest!(_022_info_flag_script {
http_server: true,
});
-itest!(_023_no_ext_with_headers {
- args: "run --reload 023_no_ext_with_headers",
- output: "023_no_ext_with_headers.out",
+itest!(_023_no_ext {
+ args: "run --reload 023_no_ext",
+ output: "023_no_ext.out",
});
// TODO(lucacasonato): remove --unstable when permissions goes stable
@@ -2018,7 +2018,7 @@ itest!(_044_bad_resource {
});
itest!(_045_proxy {
- args: "run --allow-net --allow-env --allow-run --allow-read --reload --quiet 045_proxy_test.ts",
+ args: "run -L debug --allow-net --allow-env --allow-run --allow-read --reload --quiet 045_proxy_test.ts",
output: "045_proxy_test.ts.out",
http_server: true,
});
@@ -2764,6 +2764,11 @@ itest!(tsx_imports {
output: "tsx_imports.ts.out",
});
+itest!(fix_exotic_specifiers {
+ args: "run --quiet --reload fix_exotic_specifiers.ts",
+ output: "fix_exotic_specifiers.ts.out",
+});
+
itest!(fix_js_import_js {
args: "run --quiet --reload fix_js_import_js.ts",
output: "fix_js_import_js.ts.out",
diff --git a/cli/tests/lock_check_err.out b/cli/tests/lock_check_err.out
index 87f0242f7..c12f4af34 100644
--- a/cli/tests/lock_check_err.out
+++ b/cli/tests/lock_check_err.out
@@ -1,2 +1,3 @@
-[WILDCARD]Subresource integrity check failed --lock=lock_check_err.json
-http://127.0.0.1:4545/cli/tests/003_relative_import.ts
+[WILDCARD]The source code is invalid, as it does not match the expected hash in the lock file.
+ Specifier: http://127.0.0.1:4545/cli/tests/003_relative_import.ts
+ Lock file: lock_check_err.json
diff --git a/cli/tests/lock_check_err2.out b/cli/tests/lock_check_err2.out
index 6b81c9713..d3ccfc46e 100644
--- a/cli/tests/lock_check_err2.out
+++ b/cli/tests/lock_check_err2.out
@@ -1,2 +1,3 @@
-[WILDCARD]Subresource integrity check failed --lock=lock_check_err2.json
-http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js
+[WILDCARD]The source code is invalid, as it does not match the expected hash in the lock file.
+ Specifier: http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js
+ Lock file: lock_check_err2.json
diff --git a/cli/tests/lock_dynamic_imports.out b/cli/tests/lock_dynamic_imports.out
index 57bc053b9..3bf6342c8 100644
--- a/cli/tests/lock_dynamic_imports.out
+++ b/cli/tests/lock_dynamic_imports.out
@@ -1,3 +1,4 @@
[WILDCARD]
-Subresource integrity check failed --lock=lock_dynamic_imports.json
-http://127.0.0.1:4545/cli/tests/subdir/subdir2/mod2.ts
+The source code is invalid, as it does not match the expected hash in the lock file.
+ Specifier: http://127.0.0.1:4545/cli/tests/subdir/subdir2/mod2.ts
+ Lock file: lock_dynamic_imports.json
diff --git a/cli/tests/module_graph/file_tests-importjson.ts b/cli/tests/module_graph/file_tests-importjson.ts
new file mode 100644
index 000000000..c2bc2bca7
--- /dev/null
+++ b/cli/tests/module_graph/file_tests-importjson.ts
@@ -0,0 +1,3 @@
+import * as config from "./some.json";
+
+console.log(config);
diff --git a/cli/tests/module_graph/file_tests-some.json b/cli/tests/module_graph/file_tests-some.json
new file mode 100644
index 000000000..567c4ba21
--- /dev/null
+++ b/cli/tests/module_graph/file_tests-some.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "debug": true
+ }
+}
diff --git a/cli/tests/performance_stats.out b/cli/tests/performance_stats.out
index 0fe7ba7f0..141829ee6 100644
--- a/cli/tests/performance_stats.out
+++ b/cli/tests/performance_stats.out
@@ -1,14 +1,16 @@
[WILDCARD]
-DEBUG RS - [WILDCARD] - Files: [WILDCARD]
-DEBUG RS - [WILDCARD] - Nodes: [WILDCARD]
-DEBUG RS - [WILDCARD] - Identifiers: [WILDCARD]
-DEBUG RS - [WILDCARD] - Symbols: [WILDCARD]
-DEBUG RS - [WILDCARD] - Types: [WILDCARD]
-DEBUG RS - [WILDCARD] - Instantiations: [WILDCARD]
-DEBUG RS - [WILDCARD] - Parse time: [WILDCARD]
-DEBUG RS - [WILDCARD] - Bind time: [WILDCARD]
-DEBUG RS - [WILDCARD] - Check time: [WILDCARD]
-DEBUG RS - [WILDCARD] - Emit time: [WILDCARD]
-DEBUG RS - [WILDCARD] - Total TS time: [WILDCARD]
-DEBUG RS - [WILDCARD] - Compile time: [WILDCARD]
+DEBUG RS - [WILDCARD] - Compilation statistics:
+ Files: [WILDCARD]
+ Nodes: [WILDCARD]
+ Identifiers: [WILDCARD]
+ Symbols: [WILDCARD]
+ Types: [WILDCARD]
+ Instantiations: [WILDCARD]
+ Parse time: [WILDCARD]
+ Bind time: [WILDCARD]
+ Check time: [WILDCARD]
+ Emit time: [WILDCARD]
+ Total TS time: [WILDCARD]
+ Compile time: [WILDCARD]
+
[WILDCARD]
diff --git a/cli/tests/single_compile_with_reload.ts.out b/cli/tests/single_compile_with_reload.ts.out
index 88c3f97ab..4ffaa6e77 100644
--- a/cli/tests/single_compile_with_reload.ts.out
+++ b/cli/tests/single_compile_with_reload.ts.out
@@ -1,5 +1,4 @@
Check [WILDCARD]single_compile_with_reload.ts
-Check [WILDCARD]single_compile_with_reload_dyn.ts
Hello
1
2
diff --git a/cli/tests/ts_type_only_import.ts.out b/cli/tests/ts_type_only_import.ts.out
index d7120966f..f808ed21a 100644
--- a/cli/tests/ts_type_only_import.ts.out
+++ b/cli/tests/ts_type_only_import.ts.out
@@ -1,4 +1,4 @@
Check [WILDCARD]ts_type_only_import.ts
-Warning Failed to get compiled source code of "[WILDCARD]ts_type_only_import.d.ts".
-Reason: [WILDCARD] (os error 2)
-If the source file provides only type exports, prefer to use "import type" or "export type" syntax instead.
+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.
diff --git a/cli/tests/unsupported_dynamic_import_scheme.out b/cli/tests/unsupported_dynamic_import_scheme.out
index 2a1a4e01f..0161b7a99 100644
--- a/cli/tests/unsupported_dynamic_import_scheme.out
+++ b/cli/tests/unsupported_dynamic_import_scheme.out
@@ -1,4 +1,4 @@
-error: Uncaught TypeError: Unsupported scheme "xxx" for module "xxx:". Supported schemes: [
+error: Unsupported scheme "xxx" for module "xxx:". Supported schemes: [
"http",
"https",
"file",
diff --git a/cli/tsc.rs b/cli/tsc.rs
index ac73e8886..4cf253b7c 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -2,7 +2,6 @@
use crate::ast::parse;
use crate::ast::Location;
-use crate::colors;
use crate::diagnostics::Diagnostics;
use crate::disk_cache::DiskCache;
use crate::file_fetcher::SourceFile;
@@ -14,7 +13,6 @@ use crate::module_graph::ModuleGraph;
use crate::module_graph::ModuleGraphLoader;
use crate::permissions::Permissions;
use crate::program_state::ProgramState;
-use crate::source_maps::SourceMapGetter;
use crate::tsc_config;
use crate::version;
use deno_core::error::generic_error;
@@ -29,7 +27,6 @@ use deno_core::JsRuntime;
use deno_core::ModuleSpecifier;
use deno_core::RuntimeOptions;
use log::debug;
-use log::info;
use log::Level;
use regex::Regex;
use serde::Deserialize;
@@ -231,12 +228,6 @@ pub struct CompiledFileMetadata {
}
impl CompiledFileMetadata {
- pub fn from_json_string(
- metadata_string: String,
- ) -> Result<Self, serde_json::Error> {
- serde_json::from_str::<Self>(&metadata_string)
- }
-
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
@@ -308,15 +299,6 @@ struct BundleResponse {
stats: Option<Vec<Stat>>,
}
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CompileResponse {
- diagnostics: Diagnostics,
- emit_map: HashMap<String, EmittedSource>,
- build_info: Option<String>,
- stats: Option<Vec<Stat>>,
-}
-
// TODO(bartlomieju): possible deduplicate once TS refactor is stabilized
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -360,197 +342,6 @@ impl TsCompiler {
c.insert(url.clone());
}
- fn has_compiled(&self, url: &Url) -> bool {
- let c = self.compiled.lock().unwrap();
- c.contains(url)
- }
-
- /// Check if there is compiled source in cache that is valid and can be used
- /// again.
- fn has_compiled_source(&self, url: &Url) -> bool {
- let specifier = ModuleSpecifier::from(url.clone());
- if let Some(source_file) = self
- .file_fetcher
- .fetch_cached_source_file(&specifier, Permissions::allow_all())
- {
- if let Some(metadata) = self.get_metadata(&url) {
- // Compare version hashes
- let version_hash_to_validate = source_code_version_hash(
- &source_file.source_code.as_bytes(),
- version::DENO,
- &self.config.hash.as_bytes(),
- );
-
- if metadata.version_hash == version_hash_to_validate {
- return true;
- }
- }
- }
-
- false
- }
-
- fn has_valid_cache(
- &self,
- url: &Url,
- build_info: &Option<String>,
- ) -> Result<bool, AnyError> {
- if let Some(build_info_str) = build_info.as_ref() {
- let build_inf_json: Value = serde_json::from_str(build_info_str)?;
- let program_val = build_inf_json["program"].as_object().unwrap();
- let file_infos = program_val["fileInfos"].as_object().unwrap();
-
- if !self.has_compiled_source(url) {
- return Ok(false);
- }
-
- for (filename, file_info) in file_infos.iter() {
- if filename.starts_with("asset://") {
- continue;
- }
-
- let url = Url::parse(&filename).expect("Filename is not a valid url");
- let specifier = ModuleSpecifier::from(url);
-
- if let Some(source_file) = self
- .file_fetcher
- .fetch_cached_source_file(&specifier, Permissions::allow_all())
- {
- let existing_hash = crate::checksum::gen(&[
- &source_file.source_code.as_bytes(),
- &version::DENO.as_bytes(),
- ]);
- let expected_hash =
- file_info["version"].as_str().unwrap().to_string();
- if existing_hash != expected_hash {
- // hashes don't match, somethings changed
- return Ok(false);
- }
- } else {
- // no cached source file
- return Ok(false);
- }
- }
- } else {
- // no build info
- return Ok(false);
- }
-
- Ok(true)
- }
-
- /// Asynchronously compile module and all it's dependencies.
- ///
- /// This method compiled every module at most once.
- ///
- /// If `--reload` flag was provided then compiler will not on-disk cache and
- /// force recompilation.
- ///
- /// If compilation is required then new V8 worker is spawned with fresh TS
- /// compiler.
- pub async fn compile(
- &self,
- program_state: &Arc<ProgramState>,
- source_file: &SourceFile,
- target: TargetLib,
- module_graph: &ModuleGraph,
- allow_js: bool,
- ) -> Result<(), AnyError> {
- let module_url = source_file.url.clone();
- let build_info_key = self
- .disk_cache
- .get_cache_filename_with_extension(&module_url, "buildinfo");
- let build_info = match self.disk_cache.get(&build_info_key) {
- Ok(bytes) => Some(String::from_utf8(bytes)?),
- Err(_) => None,
- };
-
- // Only use disk cache if `--reload` flag was not used or this file has
- // already been compiled during current process lifetime.
- if (self.use_disk_cache || self.has_compiled(&source_file.url))
- && self.has_valid_cache(&source_file.url, &build_info)?
- {
- return Ok(());
- }
-
- let module_graph_json =
- serde_json::to_value(module_graph).expect("Failed to serialize data");
- let target = match target {
- TargetLib::Main => "main",
- TargetLib::Worker => "worker",
- };
- let root_names = vec![module_url.to_string()];
- let unstable = self.flags.unstable;
- let performance = matches!(self.flags.log_level, Some(Level::Debug));
- let compiler_config = self.config.clone();
-
- // TODO(bartlomieju): lift this call up - TSC shouldn't print anything
- info!("{} {}", colors::green("Check"), module_url.to_string());
-
- let mut lib = if target == "main" {
- vec!["deno.window"]
- } else {
- vec!["deno.worker"]
- };
-
- if unstable {
- lib.push("deno.unstable");
- }
-
- let mut compiler_options = json!({
- "allowJs": allow_js,
- "allowNonTsExtensions": true,
- "checkJs": false,
- "esModuleInterop": true,
- "incremental": true,
- "inlineSourceMap": true,
- // TODO(lucacasonato): enable this by default in 1.5.0
- "isolatedModules": unstable,
- "jsx": "react",
- "lib": lib,
- "module": "esnext",
- "outDir": "deno://",
- "resolveJsonModule": true,
- "sourceMap": false,
- "strict": true,
- "removeComments": true,
- "target": "esnext",
- "tsBuildInfoFile": "cache:///tsbuildinfo.json",
- });
-
- tsc_config::json_merge(&mut compiler_options, &compiler_config.options);
-
- warn_ignored_options(compiler_config.maybe_ignored_options);
-
- let j = json!({
- "type": CompilerRequestType::Compile,
- "target": target,
- "rootNames": root_names,
- "performance": performance,
- "compilerOptions": compiler_options,
- "sourceFileMap": module_graph_json,
- "buildInfo": if self.use_disk_cache { build_info } else { None },
- });
-
- let req_msg = j.to_string();
-
- let json_str = execute_in_tsc(program_state.clone(), req_msg)?;
-
- let compile_response: CompileResponse = serde_json::from_str(&json_str)?;
-
- if !compile_response.diagnostics.0.is_empty() {
- return Err(generic_error(compile_response.diagnostics.to_string()));
- }
-
- maybe_log_stats(compile_response.stats);
-
- if let Some(build_info) = compile_response.build_info {
- self.cache_build_info(&module_url, build_info)?;
- }
- self.cache_emitted_files(compile_response.emit_map)?;
- Ok(())
- }
-
/// For a given module, generate a single file JavaScript output that includes
/// all the dependencies for that module.
pub async fn bundle(
@@ -666,39 +457,6 @@ impl TsCompiler {
Ok(output)
}
- /// Get associated `CompiledFileMetadata` for given module if it exists.
- fn get_metadata(&self, url: &Url) -> Option<CompiledFileMetadata> {
- // Try to load cached version:
- // 1. check if there's 'meta' file
- let cache_key = self
- .disk_cache
- .get_cache_filename_with_extension(url, "meta");
- if let Ok(metadata_bytes) = self.disk_cache.get(&cache_key) {
- if let Ok(metadata) = std::str::from_utf8(&metadata_bytes) {
- if let Ok(read_metadata) =
- CompiledFileMetadata::from_json_string(metadata.to_string())
- {
- return Some(read_metadata);
- }
- }
- }
-
- None
- }
-
- fn cache_build_info(
- &self,
- url: &Url,
- build_info: String,
- ) -> std::io::Result<()> {
- let js_key = self
- .disk_cache
- .get_cache_filename_with_extension(url, "buildinfo");
- self.disk_cache.set(&js_key, build_info.as_bytes())?;
-
- Ok(())
- }
-
fn cache_emitted_files(
&self,
emit_map: HashMap<String, EmittedSource>,
@@ -730,45 +488,6 @@ impl TsCompiler {
Ok(())
}
- pub fn get_compiled_module(
- &self,
- module_url: &Url,
- ) -> Result<CompiledModule, AnyError> {
- let compiled_source_file = self.get_compiled_source_file(module_url)?;
-
- let compiled_module = CompiledModule {
- code: compiled_source_file.source_code,
- name: module_url.to_string(),
- };
-
- Ok(compiled_module)
- }
-
- /// Return compiled JS file for given TS module.
- // TODO: ideally we shouldn't construct SourceFile by hand, but it should be
- // delegated to SourceFileFetcher.
- pub fn get_compiled_source_file(
- &self,
- module_url: &Url,
- ) -> Result<SourceFile, AnyError> {
- let cache_key = self
- .disk_cache
- .get_cache_filename_with_extension(&module_url, "js");
- let compiled_code = self.disk_cache.get(&cache_key)?;
- let compiled_code_filename = self.disk_cache.location.join(cache_key);
- debug!("compiled filename: {:?}", compiled_code_filename);
-
- let compiled_module = SourceFile {
- url: module_url.clone(),
- filename: compiled_code_filename,
- media_type: MediaType::JavaScript,
- source_code: String::from_utf8(compiled_code)?,
- types_header: None,
- };
-
- Ok(compiled_module)
- }
-
/// Save compiled JS file for given TS module to on-disk cache.
///
/// Along compiled file a special metadata file is saved as well containing
@@ -801,31 +520,6 @@ impl TsCompiler {
)
}
- /// Return associated source map file for given TS module.
- // TODO: ideally we shouldn't construct SourceFile by hand, but it should be delegated to
- // SourceFileFetcher
- pub fn get_source_map_file(
- &self,
- module_specifier: &ModuleSpecifier,
- ) -> Result<SourceFile, AnyError> {
- let cache_key = self
- .disk_cache
- .get_cache_filename_with_extension(module_specifier.as_url(), "js.map");
- let source_code = self.disk_cache.get(&cache_key)?;
- let source_map_filename = self.disk_cache.location.join(cache_key);
- debug!("source map filename: {:?}", source_map_filename);
-
- let source_map_file = SourceFile {
- url: module_specifier.as_url().to_owned(),
- filename: source_map_filename,
- media_type: MediaType::JavaScript,
- source_code: String::from_utf8(source_code)?,
- types_header: None,
- };
-
- Ok(source_map_file)
- }
-
/// Save source map file for given TS module to on-disk cache.
fn cache_source_map(
&self,
@@ -856,91 +550,6 @@ impl TsCompiler {
}
}
-impl SourceMapGetter for TsCompiler {
- fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>> {
- self.try_to_resolve_and_get_source_map(script_name)
- }
-
- fn get_source_line(&self, script_name: &str, line: usize) -> Option<String> {
- self
- .try_resolve_and_get_source_file(script_name)
- .map(|out| {
- // Do NOT use .lines(): it skips the terminating empty line.
- // (due to internally using .split_terminator() instead of .split())
- let lines: Vec<&str> = out.source_code.split('\n').collect();
- assert!(lines.len() > line);
- lines[line].to_string()
- })
- }
-}
-
-// `SourceMapGetter` related methods
-impl TsCompiler {
- fn try_to_resolve(&self, script_name: &str) -> Option<ModuleSpecifier> {
- // if `script_name` can't be resolved to ModuleSpecifier it's probably internal
- // script (like `gen/cli/bundle/compiler.js`) so we won't be
- // able to get source for it anyway
- ModuleSpecifier::resolve_url(script_name).ok()
- }
-
- fn try_resolve_and_get_source_file(
- &self,
- script_name: &str,
- ) -> Option<SourceFile> {
- if let Some(module_specifier) = self.try_to_resolve(script_name) {
- return self
- .file_fetcher
- .fetch_cached_source_file(&module_specifier, Permissions::allow_all());
- }
-
- None
- }
-
- fn try_to_resolve_and_get_source_map(
- &self,
- script_name: &str,
- ) -> Option<Vec<u8>> {
- if let Some(module_specifier) = self.try_to_resolve(script_name) {
- if module_specifier.as_url().scheme() == "deno" {
- return None;
- }
- return match self.get_source_map_file(&module_specifier) {
- Ok(out) => Some(out.source_code.into_bytes()),
- Err(_) => {
- // Check if map is inlined
- if let Ok(compiled_source) =
- self.get_compiled_module(module_specifier.as_url())
- {
- let mut content_lines = compiled_source
- .code
- .split('\n')
- .map(|s| s.to_string())
- .collect::<Vec<String>>();
-
- if !content_lines.is_empty() {
- let last_line = content_lines.pop().unwrap();
- if last_line.starts_with(
- "//# sourceMappingURL=data:application/json;base64,",
- ) {
- let encoded = last_line.trim_start_matches(
- "//# sourceMappingURL=data:application/json;base64,",
- );
- let decoded_map =
- base64::decode(encoded).expect("failed to parse source map");
- return Some(decoded_map);
- }
- }
- }
-
- None
- }
- };
- }
-
- None
- }
-}
-
#[derive(Debug, Deserialize)]
struct CreateHashArgs {
data: String,
@@ -1425,7 +1034,6 @@ fn parse_deno_types(comment: &str) -> Option<String> {
#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum CompilerRequestType {
- Compile = 0,
Bundle = 1,
RuntimeCompile = 2,
RuntimeBundle = 3,
@@ -1438,7 +1046,6 @@ impl Serialize for CompilerRequestType {
S: Serializer,
{
let value: i32 = match self {
- CompilerRequestType::Compile => 0 as i32,
CompilerRequestType::Bundle => 1 as i32,
CompilerRequestType::RuntimeCompile => 2 as i32,
CompilerRequestType::RuntimeBundle => 3 as i32,
@@ -1451,12 +1058,8 @@ impl Serialize for CompilerRequestType {
#[cfg(test)]
mod tests {
use super::*;
- use crate::deno_dir;
use crate::fs as deno_fs;
- use crate::http_cache;
use crate::program_state::ProgramState;
- use deno_core::ModuleSpecifier;
- use std::path::PathBuf;
use tempfile::TempDir;
#[test]
@@ -1517,75 +1120,6 @@ mod tests {
}
#[tokio::test]
- async fn test_compile() {
- let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
- .parent()
- .unwrap()
- .join("cli/tests/002_hello.ts");
- let specifier =
- ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap();
- let out = SourceFile {
- url: specifier.as_url().clone(),
- filename: PathBuf::from(p.to_str().unwrap().to_string()),
- media_type: MediaType::TypeScript,
- source_code: include_str!("./tests/002_hello.ts").to_string(),
- types_header: None,
- };
- let dir =
- deno_dir::DenoDir::new(Some(test_util::new_deno_dir().path().to_owned()))
- .unwrap();
- let http_cache = http_cache::HttpCache::new(&dir.root.join("deps"));
- let mock_state = ProgramState::mock(
- vec![String::from("deno"), String::from("hello.ts")],
- None,
- );
- let file_fetcher = SourceFileFetcher::new(
- http_cache,
- true,
- mock_state.flags.cache_blocklist.clone(),
- false,
- false,
- None,
- )
- .unwrap();
-
- let mut module_graph_loader = ModuleGraphLoader::new(
- file_fetcher.clone(),
- None,
- Permissions::allow_all(),
- false,
- false,
- );
- module_graph_loader
- .add_to_graph(&specifier, None)
- .await
- .expect("Failed to create graph");
- let module_graph = module_graph_loader.get_graph();
-
- let ts_compiler = TsCompiler::new(
- file_fetcher,
- mock_state.flags.clone(),
- dir.gen_cache.clone(),
- )
- .unwrap();
-
- let result = ts_compiler
- .compile(&mock_state, &out, TargetLib::Main, &module_graph, false)
- .await;
- assert!(result.is_ok());
- let compiled_file = ts_compiler.get_compiled_module(&out.url).unwrap();
- let source_code = compiled_file.code;
- assert!(source_code
- .as_bytes()
- .starts_with(b"\"use strict\";\nconsole.log(\"Hello World\");"));
- let mut lines: Vec<String> =
- source_code.split('\n').map(|s| s.to_string()).collect();
- let last_line = lines.pop().unwrap();
- assert!(last_line
- .starts_with("//# sourceMappingURL=data:application/json;base64"));
- }
-
- #[tokio::test]
async fn test_bundle() {
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index 91bce61e3..86a68a6bd 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -163,8 +163,9 @@ delete Object.prototype.__proto__;
4: "TSX",
5: "Json",
6: "Wasm",
- 7: "BuildInfo",
- 8: "Unknown",
+ 7: "TsBuildInfo",
+ 8: "SourceMap",
+ 9: "Unknown",
JavaScript: 0,
JSX: 1,
TypeScript: 2,
@@ -172,8 +173,9 @@ delete Object.prototype.__proto__;
TSX: 4,
Json: 5,
Wasm: 6,
- BuildInfo: 7,
- Unknown: 6,
+ TsBuildInfo: 7,
+ SourceMap: 8,
+ Unknown: 9,
};
function getExtension(fileName, mediaType) {
@@ -183,7 +185,9 @@ delete Object.prototype.__proto__;
case MediaType.JSX:
return ts.Extension.Jsx;
case MediaType.TypeScript:
- return fileName.endsWith(".d.ts") ? ts.Extension.Dts : ts.Extension.Ts;
+ return ts.Extension.Ts;
+ case MediaType.Dts:
+ return ts.Extension.Dts;
case MediaType.TSX:
return ts.Extension.Tsx;
case MediaType.Wasm:
@@ -366,7 +370,7 @@ delete Object.prototype.__proto__;
}
/** @type {{ data: string; hash: string; }} */
- const { data, hash } = core.jsonOpSync(
+ const { data, hash, scriptKind } = core.jsonOpSync(
"op_load",
{ specifier },
);
@@ -375,6 +379,8 @@ delete Object.prototype.__proto__;
specifier,
data,
languageVersion,
+ false,
+ scriptKind,
);
sourceFile.moduleName = specifier;
sourceFile.version = hash;
@@ -406,7 +412,6 @@ delete Object.prototype.__proto__;
let maybeSpecifiers;
if (sourceFiles) {
maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName);
- debug(` specifiers: ${maybeSpecifiers.join(", ")}`);
}
return core.jsonOpSync(
"op_emit",
@@ -465,11 +470,12 @@ delete Object.prototype.__proto__;
specifiers,
base,
});
- return resolved.map(([resolvedFileName, extension]) => ({
+ let r = resolved.map(([resolvedFileName, extension]) => ({
resolvedFileName,
extension,
isExternalLibraryImport: false,
}));
+ return r;
}
},
createHash(data) {
@@ -649,7 +655,6 @@ delete Object.prototype.__proto__;
// Warning! The values in this enum are duplicated in `cli/msg.rs`
// Update carefully!
const CompilerRequestType = {
- Compile: 0,
Bundle: 1,
RuntimeCompile: 2,
RuntimeBundle: 3,
@@ -671,25 +676,6 @@ delete Object.prototype.__proto__;
};
}
- function createCompileWriteFile(state) {
- return function writeFile(fileName, data, sourceFiles) {
- const isBuildInfo = fileName === TS_BUILD_INFO;
-
- if (isBuildInfo) {
- assert(isBuildInfo);
- state.buildInfo = data;
- return;
- }
-
- assert(sourceFiles);
- assert(sourceFiles.length === 1);
- state.emitMap[fileName] = {
- filename: sourceFiles[0].fileName,
- contents: data,
- };
- };
- }
-
function createRuntimeCompileWriteFile(state) {
return function writeFile(fileName, data, sourceFiles) {
assert(sourceFiles);
@@ -959,101 +945,6 @@ delete Object.prototype.__proto__;
.map((sym) => sym.getName());
}
- function compile({
- buildInfo,
- compilerOptions,
- rootNames,
- target,
- sourceFileMap,
- type,
- performance,
- }) {
- if (performance) {
- performanceStart();
- }
- debug(">>> compile start", { rootNames, type: CompilerRequestType[type] });
-
- // When a programme is emitted, TypeScript will call `writeFile` with
- // each file that needs to be emitted. The Deno compiler host delegates
- // this, to make it easier to perform the right actions, which vary
- // based a lot on the request.
- const state = {
- rootNames,
- emitMap: {},
- };
-
- let diagnostics = [];
-
- const { options, diagnostics: diags } = parseCompilerOptions(
- compilerOptions,
- );
-
- diagnostics = diags.filter(
- ({ code }) => code != 5023 && !IGNORED_DIAGNOSTICS.includes(code),
- );
-
- // TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
- // however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
- options.allowNonTsExtensions = true;
-
- legacyHostState.target = target;
- legacyHostState.writeFile = createCompileWriteFile(state);
- legacyHostState.buildInfo = buildInfo;
-
- buildSourceFileCache(sourceFileMap);
- // if there was a configuration and no diagnostics with it, we will continue
- // to generate the program and possibly emit it.
- if (diagnostics.length === 0) {
- const program = ts.createIncrementalProgram({
- rootNames,
- options,
- host,
- });
-
- // TODO(bartlomieju): check if this is ok
- diagnostics = [
- ...program.getConfigFileParsingDiagnostics(),
- ...program.getSyntacticDiagnostics(),
- ...program.getOptionsDiagnostics(),
- ...program.getGlobalDiagnostics(),
- ...program.getSemanticDiagnostics(),
- ];
- diagnostics = diagnostics.filter(
- ({ code }) =>
- !IGNORED_DIAGNOSTICS.includes(code) &&
- !IGNORED_COMPILE_DIAGNOSTICS.includes(code),
- );
-
- // We will only proceed with the emit if there are no diagnostics.
- if (diagnostics.length === 0) {
- const emitResult = program.emit();
- // If `checkJs` is off we still might be compiling entry point JavaScript file
- // (if it has `.ts` imports), but it won't be emitted. In that case we skip
- // assertion.
- if (options.checkJs) {
- assert(
- emitResult.emitSkipped === false,
- "Unexpected skip of the emit.",
- );
- }
- // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
- // without casting.
- diagnostics = emitResult.diagnostics;
- }
- performanceProgram({ program });
- }
-
- debug("<<< compile end", { rootNames, type: CompilerRequestType[type] });
- const stats = performance ? performanceEnd() : undefined;
-
- return {
- emitMap: state.emitMap,
- buildInfo: state.buildInfo,
- diagnostics: fromTypeScriptDiagnostic(diagnostics),
- stats,
- };
- }
-
function bundle({
compilerOptions,
rootNames,
@@ -1296,11 +1187,6 @@ delete Object.prototype.__proto__;
function tsCompilerOnMessage(msg) {
const request = msg.data;
switch (request.type) {
- case CompilerRequestType.Compile: {
- const result = compile(request);
- opCompilerRespond(result);
- break;
- }
case CompilerRequestType.Bundle: {
const result = bundle(request);
opCompilerRespond(result);
diff --git a/cli/tsc2.rs b/cli/tsc2.rs
index 64563ce01..b3c14d631 100644
--- a/cli/tsc2.rs
+++ b/cli/tsc2.rs
@@ -21,6 +21,7 @@ use deno_core::RuntimeOptions;
use deno_core::Snapshot;
use serde::Deserialize;
use serde::Serialize;
+use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug, Clone, Default, Eq, PartialEq)]
@@ -40,7 +41,7 @@ pub struct Request {
/// Indicates to the tsc runtime if debug logging should occur.
pub debug: bool,
#[serde(skip_serializing)]
- pub graph: Rc<Graph2>,
+ pub graph: Rc<RefCell<Graph2>>,
#[serde(skip_serializing)]
pub hash_data: Vec<Vec<u8>>,
#[serde(skip_serializing)]
@@ -65,14 +66,14 @@ pub struct Response {
struct State {
hash_data: Vec<Vec<u8>>,
emitted_files: Vec<EmittedFile>,
- graph: Rc<Graph2>,
+ graph: Rc<RefCell<Graph2>>,
maybe_tsbuildinfo: Option<String>,
maybe_response: Option<RespondArgs>,
}
impl State {
pub fn new(
- graph: Rc<Graph2>,
+ graph: Rc<RefCell<Graph2>>,
hash_data: Vec<Vec<u8>>,
maybe_tsbuildinfo: Option<String>,
) -> Self {
@@ -162,10 +163,23 @@ fn load(state: &mut State, args: Value) -> Result<Value, AnyError> {
let specifier = ModuleSpecifier::resolve_url_or_path(&v.specifier)
.context("Error converting a string module specifier for \"op_load\".")?;
let mut hash: Option<String> = None;
+ let mut media_type = MediaType::Unknown;
let data = if &v.specifier == "deno:///.tsbuildinfo" {
state.maybe_tsbuildinfo.clone()
+ // in certain situations we return a "blank" module to tsc and we need to
+ // handle the request for that module here.
+ } else if &v.specifier == "deno:///none.d.ts" {
+ hash = Some("1".to_string());
+ media_type = MediaType::TypeScript;
+ Some("declare var a: any;\nexport = a;\n".to_string())
} else {
- let maybe_source = state.graph.get_source(&specifier);
+ let graph = state.graph.borrow();
+ let maybe_source = graph.get_source(&specifier);
+ media_type = if let Some(media_type) = graph.get_media_type(&specifier) {
+ media_type
+ } else {
+ MediaType::Unknown
+ };
if let Some(source) = &maybe_source {
let mut data = vec![source.as_bytes().to_owned()];
data.extend_from_slice(&state.hash_data);
@@ -174,7 +188,9 @@ fn load(state: &mut State, args: Value) -> Result<Value, AnyError> {
maybe_source
};
- Ok(json!({ "data": data, "hash": hash }))
+ Ok(
+ json!({ "data": data, "hash": hash, "scriptKind": media_type.as_ts_script_kind() }),
+ )
}
#[derive(Debug, Deserialize)]
@@ -201,19 +217,31 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
MediaType::from(specifier).as_ts_extension().to_string(),
));
} else {
- let resolved_specifier = state.graph.resolve(specifier, &referrer)?;
- let media_type = if let Some(media_type) =
- state.graph.get_media_type(&resolved_specifier)
- {
- media_type
- } else {
- bail!(
- "Unable to resolve media type for specifier: \"{}\"",
- resolved_specifier
- )
- };
- resolved
- .push((resolved_specifier.to_string(), media_type.as_ts_extension()));
+ let graph = state.graph.borrow();
+ 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
+ )
+ };
+ resolved.push((
+ resolved_specifier.to_string(),
+ media_type.as_ts_extension(),
+ ));
+ }
+ // in certain situations, like certain dynamic imports, we won't have
+ // the source file in the graph, so we will return a fake module to
+ // make tsc happy.
+ Err(_) => {
+ resolved.push(("deno:///none.d.ts".to_string(), ".d.ts".to_string()));
+ }
+ }
}
}
@@ -221,7 +249,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
-pub struct RespondArgs {
+struct RespondArgs {
pub diagnostics: Diagnostics,
pub stats: Stats,
}
@@ -269,9 +297,7 @@ pub fn exec(
runtime
.execute("[native code]", startup_source)
.context("Could not properly start the compiler runtime.")?;
- runtime
- .execute("[native_code]", &exec_source)
- .context("Execute request failed.")?;
+ runtime.execute("[native_code]", &exec_source)?;
let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
@@ -324,10 +350,10 @@ mod tests {
}));
let mut builder = GraphBuilder2::new(handler.clone(), None);
builder
- .insert(&specifier)
+ .add(&specifier, false)
.await
.expect("module not inserted");
- let graph = Rc::new(builder.get_graph(&None).expect("could not get graph"));
+ let graph = Rc::new(RefCell::new(builder.get_graph(&None)));
State::new(graph, hash_data, maybe_tsbuildinfo)
}
@@ -410,7 +436,8 @@ mod tests {
actual,
json!({
"data": "console.log(\"hello deno\");\n",
- "hash": "149c777056afcc973d5fcbe11421b6d5ddc57b81786765302030d7fc893bf729"
+ "hash": "149c777056afcc973d5fcbe11421b6d5ddc57b81786765302030d7fc893bf729",
+ "scriptKind": 3,
})
);
}
@@ -433,7 +460,8 @@ mod tests {
actual,
json!({
"data": "some content",
- "hash": null
+ "hash": null,
+ "scriptKind": 0,
})
);
}
@@ -451,6 +479,7 @@ mod tests {
json!({
"data": null,
"hash": null,
+ "scriptKind": 0,
})
)
}
@@ -475,7 +504,7 @@ mod tests {
}
#[tokio::test]
- async fn test_resolve_error() {
+ async fn test_resolve_empty() {
let mut state = setup(
Some(
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a.ts")
@@ -485,10 +514,11 @@ mod tests {
None,
)
.await;
- resolve(
+ let actual = resolve(
&mut state,
json!({ "base": "https://deno.land/x/a.ts", "specifiers": [ "./bad.ts" ]}),
- ).expect_err("should have errored");
+ ).expect("should have not errored");
+ assert_eq!(actual, json!([["deno:///none.d.ts", ".d.ts"]]));
}
#[tokio::test]
@@ -544,17 +574,16 @@ mod tests {
}));
let mut builder = GraphBuilder2::new(handler.clone(), None);
builder
- .insert(&specifier)
+ .add(&specifier, false)
.await
.expect("module not inserted");
- let graph = Rc::new(builder.get_graph(&None).expect("could not get graph"));
+ let graph = Rc::new(RefCell::new(builder.get_graph(&None)));
let config = TsConfig::new(json!({
"allowJs": true,
"checkJs": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"incremental": true,
- "isolatedModules": true,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
diff --git a/cli/tsc_config.rs b/cli/tsc_config.rs
index 15a172a72..9d05c33f7 100644
--- a/cli/tsc_config.rs
+++ b/cli/tsc_config.rs
@@ -214,6 +214,21 @@ impl TsConfig {
self.0.to_string().as_bytes().to_owned()
}
+ /// Return the value of the `checkJs` compiler option, defaulting to `false`
+ /// if not present.
+ pub fn get_check_js(&self) -> bool {
+ if let Some(check_js) = self.0.get("checkJs") {
+ check_js.as_bool().unwrap_or(false)
+ } else {
+ false
+ }
+ }
+
+ /// Merge a serde_json value into the configuration.
+ pub fn merge(&mut self, value: &Value) {
+ json_merge(&mut self.0, value);
+ }
+
/// Take an optional string representing a user provided TypeScript config file
/// which was passed in via the `--config` compiler option and merge it with
/// the configuration. Returning the result which optionally contains any
diff --git a/cli/worker.rs b/cli/worker.rs
index 877af3208..a8722e7a4 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -121,7 +121,7 @@ impl Worker {
module_loader: Some(module_loader),
startup_snapshot: Some(startup_snapshot),
js_error_create_fn: Some(Box::new(move |core_js_error| {
- JsError::create(core_js_error, &global_state_.ts_compiler)
+ JsError::create(core_js_error, global_state_.clone())
})),
..Default::default()
});