1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

refactor(cli): migrate run and cache to new infrastructure (#7996)

Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
This commit is contained in:
Kitson Kelly 2020-10-23 11:50:15 +11:00 committed by GitHub
parent 9fa59f0ca8
commit 7e2c7fb6c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1181 additions and 1299 deletions

View file

@ -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. /// A buffer for collecting diagnostic messages from the AST parser.
#[derive(Debug)] #[derive(Debug)]
pub struct DiagnosticBuffer(Vec<String>); pub struct DiagnosticBuffer(Vec<String>);

View file

@ -107,8 +107,9 @@ impl DiskCache {
} }
scheme => { scheme => {
unimplemented!( unimplemented!(
"Don't know how to create cache name for scheme: {}", "Don't know how to create cache name for scheme: {}\n Url: {}",
scheme scheme,
url
); );
} }
}; };

View file

@ -579,6 +579,29 @@ fn map_js_like_extension(path: &Path, default: MediaType) -> MediaType {
None => default, None => default,
Some("jsx") => MediaType::JSX, Some("jsx") => MediaType::JSX,
Some("tsx") => MediaType::TSX, 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, Some(_) => default,
}, },
} }
@ -1564,7 +1587,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
map_content_type(Path::new("foo/bar.d.ts"), None).0, map_content_type(Path::new("foo/bar.d.ts"), None).0,
MediaType::TypeScript MediaType::Dts
); );
assert_eq!( assert_eq!(
map_content_type(Path::new("foo/bar.js"), None).0, map_content_type(Path::new("foo/bar.js"), None).0,
@ -1741,6 +1764,26 @@ mod tests {
.0, .0,
MediaType::JSX 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] #[test]

View file

@ -7,6 +7,7 @@ use deno_core::error::{AnyError, JsError as CoreJsError, JsStackFrame};
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc;
const SOURCE_ABBREV_THRESHOLD: usize = 150; const SOURCE_ABBREV_THRESHOLD: usize = 150;
@ -237,7 +238,7 @@ pub struct JsError(CoreJsError);
impl JsError { impl JsError {
pub fn create( pub fn create(
core_js_error: CoreJsError, core_js_error: CoreJsError,
source_map_getter: &impl SourceMapGetter, source_map_getter: Arc<impl SourceMapGetter>,
) -> AnyError { ) -> AnyError {
let core_js_error = apply_source_map(&core_js_error, source_map_getter); let core_js_error = apply_source_map(&core_js_error, source_map_getter);
let js_error = Self(core_js_error); let js_error = Self(core_js_error);

View file

@ -51,7 +51,7 @@ mod test_runner;
mod text_encoding; mod text_encoding;
mod tokio_util; mod tokio_util;
mod tsc; mod tsc;
pub mod tsc2; mod tsc2;
mod tsc_config; mod tsc_config;
mod upgrade; mod upgrade;
mod version; mod version;
@ -174,14 +174,16 @@ async fn info_command(
let specifier = ModuleSpecifier::resolve_url_or_path(&specifier)?; let specifier = ModuleSpecifier::resolve_url_or_path(&specifier)?;
let handler = Rc::new(RefCell::new(specifier_handler::FetchHandler::new( let handler = Rc::new(RefCell::new(specifier_handler::FetchHandler::new(
&program_state, &program_state,
// info accesses dynamically imported modules just for their information
// so we allow access to all of them.
Permissions::allow_all(), Permissions::allow_all(),
)?)); )?));
let mut builder = module_graph2::GraphBuilder2::new( let mut builder = module_graph2::GraphBuilder2::new(
handler, handler,
program_state.maybe_import_map.clone(), program_state.maybe_import_map.clone(),
); );
builder.insert(&specifier).await?; builder.add(&specifier, false).await?;
let graph = builder.get_graph(&program_state.lockfile)?; let graph = builder.get_graph(&program_state.lockfile);
let info = graph.info()?; let info = graph.info()?;
if json { if json {
@ -312,14 +314,16 @@ async fn bundle_command(
let output = if flags.no_check { let output = if flags.no_check {
let handler = Rc::new(RefCell::new(FetchHandler::new( let handler = Rc::new(RefCell::new(FetchHandler::new(
&program_state, &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(), Permissions::allow_all(),
)?)); )?));
let mut builder = module_graph2::GraphBuilder2::new( let mut builder = module_graph2::GraphBuilder2::new(
handler, handler,
program_state.maybe_import_map.clone(), program_state.maybe_import_map.clone(),
); );
builder.insert(&module_specifier).await?; builder.add(&module_specifier, false).await?;
let graph = builder.get_graph(&program_state.lockfile)?; let graph = builder.get_graph(&program_state.lockfile);
let (s, stats, maybe_ignored_options) = let (s, stats, maybe_ignored_options) =
graph.bundle(module_graph2::BundleOptions { graph.bundle(module_graph2::BundleOptions {

View file

@ -77,7 +77,19 @@ impl MediaType {
}, },
}, },
Some(os_str) => match os_str.to_str() { 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("tsx") => MediaType::TSX,
Some("js") => MediaType::JavaScript, Some("js") => MediaType::JavaScript,
Some("jsx") => MediaType::JSX, Some("jsx") => MediaType::JSX,
@ -121,6 +133,19 @@ impl MediaType {
ext.into() 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 { impl Serialize for MediaType {
@ -167,10 +192,7 @@ mod tests {
MediaType::TypeScript MediaType::TypeScript
); );
assert_eq!(MediaType::from(Path::new("foo/bar.tsx")), MediaType::TSX); assert_eq!(MediaType::from(Path::new("foo/bar.tsx")), MediaType::TSX);
assert_eq!( assert_eq!(MediaType::from(Path::new("foo/bar.d.ts")), MediaType::Dts);
MediaType::from(Path::new("foo/bar.d.ts")),
MediaType::TypeScript
);
assert_eq!( assert_eq!(
MediaType::from(Path::new("foo/bar.js")), MediaType::from(Path::new("foo/bar.js")),
MediaType::JavaScript MediaType::JavaScript

File diff suppressed because it is too large Load diff

View file

@ -83,7 +83,7 @@ impl ModuleLoader for CliModuleLoader {
op_state: Rc<RefCell<OpState>>, op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier, module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>, maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool, _is_dynamic: bool,
) -> Pin<Box<deno_core::ModuleSourceFuture>> { ) -> Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.to_owned(); let module_specifier = module_specifier.to_owned();
let module_url_specified = module_specifier.to_string(); let module_url_specified = module_specifier.to_string();
@ -92,11 +92,10 @@ impl ModuleLoader for CliModuleLoader {
state.borrow::<Arc<ProgramState>>().clone() 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 fut = async move {
let compiled_module = program_state let compiled_module = program_state
.fetch_compiled_module(module_specifier, maybe_referrer) .fetch_compiled_module(module_specifier, maybe_referrer)?;
.await?;
Ok(deno_core::ModuleSource { Ok(deno_core::ModuleSource {
// Real module name, might be different from initial specifier // Real module name, might be different from initial specifier
// due to redirections. // due to redirections.
@ -113,44 +112,28 @@ impl ModuleLoader for CliModuleLoader {
&self, &self,
op_state: Rc<RefCell<OpState>>, op_state: Rc<RefCell<OpState>>,
_load_id: ModuleLoadId, _load_id: ModuleLoadId,
module_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_referrer: Option<String>, _maybe_referrer: Option<String>,
is_dyn_import: bool, is_dynamic: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> { ) -> 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 target_lib = self.target_lib.clone();
let maybe_import_map = self.import_map.clone(); let maybe_import_map = self.import_map.clone();
let state = op_state.borrow(); let state = op_state.borrow();
// Only "main" module is loaded without permission check, // The permissions that should be applied to any dynamically imported module
// ie. module that is associated with "is_main" state let dynamic_permissions = state.borrow::<Permissions>().clone();
// and is not a dynamic import.
let permissions = if self.is_main && !is_dyn_import {
Permissions::allow_all()
} else {
state.borrow::<Permissions>().clone()
};
let program_state = state.borrow::<Arc<ProgramState>>().clone(); let program_state = state.borrow::<Arc<ProgramState>>().clone();
drop(state); 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 // TODO(bartlomieju): `prepare_module_load` should take `load_id` param
async move { async move {
program_state program_state
.prepare_module_load( .prepare_module_load(
module_specifier, specifier,
maybe_referrer,
target_lib, target_lib,
permissions, dynamic_permissions,
is_dyn_import, is_dynamic,
maybe_import_map, maybe_import_map,
) )
.await .await

View file

@ -39,7 +39,7 @@ fn op_apply_source_map(
args.line_number.into(), args.line_number.into(),
args.column_number.into(), args.column_number.into(),
&mut mappings_map, &mut mappings_map,
&super::program_state(state).ts_compiler, super::program_state(state),
); );
Ok(json!({ Ok(json!({

View file

@ -8,16 +8,20 @@ use crate::import_map::ImportMap;
use crate::inspector::InspectorServer; use crate::inspector::InspectorServer;
use crate::lockfile::Lockfile; use crate::lockfile::Lockfile;
use crate::media_type::MediaType; use crate::media_type::MediaType;
use crate::module_graph::ModuleGraphFile; use crate::module_graph2::CheckOptions;
use crate::module_graph::ModuleGraphLoader;
use crate::module_graph2::GraphBuilder2; use crate::module_graph2::GraphBuilder2;
use crate::module_graph2::TranspileOptions; use crate::module_graph2::TranspileOptions;
use crate::module_graph2::TypeLib;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::source_maps::SourceMapGetter;
use crate::specifier_handler::FetchHandler; use crate::specifier_handler::FetchHandler;
use crate::tsc::CompiledModule; use crate::tsc::CompiledModule;
use crate::tsc::TargetLib; use crate::tsc::TargetLib;
use crate::tsc::TsCompiler; use crate::tsc::TsCompiler;
use deno_core::error::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::url::Url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use std::cell::RefCell; use std::cell::RefCell;
use std::env; use std::env;
@ -115,89 +119,66 @@ impl ProgramState {
/// and traspilation. /// and traspilation.
pub async fn prepare_module_load( pub async fn prepare_module_load(
self: &Arc<Self>, self: &Arc<Self>,
module_specifier: ModuleSpecifier, specifier: ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
target_lib: TargetLib, target_lib: TargetLib,
permissions: Permissions, dynamic_permissions: Permissions,
is_dyn_import: bool, is_dynamic: bool,
maybe_import_map: Option<ImportMap>, maybe_import_map: Option<ImportMap>,
) -> Result<(), AnyError> { ) -> 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 { 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) = let (stats, maybe_ignored_options) =
graph.transpile(TranspileOptions { graph.transpile(TranspileOptions {
debug: self.flags.log_level == Some(log::Level::Debug), debug,
maybe_config_path: self.flags.config_path.clone(), maybe_config_path,
reload: self.flags.reload,
})?; })?;
debug!("{}", stats);
if let Some(ignored_options) = maybe_ignored_options { if let Some(ignored_options) = maybe_ignored_options {
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
debug!("{}", stats);
} else { } else {
let mut module_graph_loader = ModuleGraphLoader::new( let lib = match target_lib {
self.file_fetcher.clone(), TargetLib::Main => {
maybe_import_map, if self.flags.unstable {
permissions.clone(), TypeLib::UnstableDenoWindow
is_dyn_import, } else {
false, TypeLib::DenoWindow
);
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);
} }
} }
} 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,
})?;
// Check if we need to compile files. debug!("{}", stats);
let should_compile = needs_compilation( if let Some(ignored_options) = maybe_ignored_options {
self.ts_compiler.compile_js, eprintln!("{}", ignored_options);
out.media_type,
&module_graph_files,
);
let allow_js = should_allow_js(&module_graph_files);
if should_compile {
self
.ts_compiler
.compile(self, &out, target_lib, &module_graph, allow_js)
.await?;
} }
} if !diagnostics.0.is_empty() {
return Err(generic_error(diagnostics.to_string()));
}
};
if let Some(ref lockfile) = self.lockfile { if let Some(ref lockfile) = self.lockfile {
let g = lockfile.lock().unwrap(); let g = lockfile.lock().unwrap();
@ -207,44 +188,39 @@ impl ProgramState {
Ok(()) Ok(())
} }
// TODO(bartlomieju): this method doesn't need to be async anymore pub fn fetch_compiled_module(
/// 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(
&self, &self,
module_specifier: ModuleSpecifier, module_specifier: ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>, maybe_referrer: Option<ModuleSpecifier>,
) -> Result<CompiledModule, AnyError> { ) -> Result<CompiledModule, AnyError> {
let out = self let out = self
.file_fetcher .file_fetcher
.fetch_cached_source_file(&module_specifier, Permissions::allow_all()) .fetch_cached_source_file(&module_specifier, Permissions::allow_all())
.expect("Cached source file doesn't exist"); .expect("Cached source file doesn't exist");
// Check if we need to compile files let url = out.url.clone();
let was_compiled = match out.media_type { let compiled_module = if let Some((code, _)) = self.get_emit(&url) {
MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true, CompiledModule {
MediaType::JavaScript => self.ts_compiler.compile_js, code: String::from_utf8(code).unwrap(),
_ => false, name: out.url.to_string(),
}; }
// We expect a compiled source for any non-JavaScript files, except for
let compiled_module = if was_compiled { // local files that have an unknown media type and no referrer (root modules
match self.ts_compiler.get_compiled_module(&out.url) { // that do not have an extension.)
Ok(module) => module, } else if out.media_type != MediaType::JavaScript
Err(e) => { && !(out.media_type == MediaType::Unknown
let msg = format!( && maybe_referrer.is_none()
"Failed to get compiled source code of \"{}\".\nReason: {}\n\ && url.scheme() == "file")
If the source file provides only type exports, prefer to use \"import type\" or \"export type\" syntax instead.", {
out.url, e.to_string() 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)
info!("{} {}", crate::colors::yellow("Warning"), msg); } 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)
CompiledModule { };
code: "".to_string(), info!("{}: {}", crate::colors::yellow("warning"), message);
name: out.url.to_string(), CompiledModule {
} code: "".to_string(),
} name: out.url.to_string(),
} }
} else { } else {
CompiledModule { CompiledModule {
@ -256,6 +232,37 @@ impl ProgramState {
Ok(compiled_module) 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. /// Quits the process if the --unstable flag was not provided.
/// ///
/// This is intentionally a non-recoverable check so that people cannot probe /// 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 // TODO(@kitsonk) this is only temporary, but should be refactored to somewhere
/// is the case when there's either: // else, like a refactored file_fetcher.
/// - a JavaScript file with non-JavaScript import impl SourceMapGetter for ProgramState {
/// - JSX import fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
fn should_allow_js(module_graph_files: &[&ModuleGraphFile]) -> bool { if let Ok(specifier) = ModuleSpecifier::resolve_url(file_name) {
module_graph_files.iter().any(|module_file| { if let Some((code, maybe_map)) = self.get_emit(&specifier.as_url()) {
if module_file.media_type == MediaType::JSX { if maybe_map.is_some() {
true maybe_map
} else if module_file.media_type == MediaType::JavaScript { } else {
module_file.imports.iter().any(|import_desc| { let code = String::from_utf8(code).unwrap();
let import_file = module_graph_files let lines: Vec<&str> = code.split('\n').collect();
.iter() if let Some(last_line) = lines.last() {
.find(|f| { if last_line
f.specifier == import_desc.resolved_specifier.to_string().as_str() .starts_with("//# sourceMappingURL=data:application/json;base64,")
}) {
.expect("Failed to find imported file"); let input = last_line.trim_start_matches(
let media_type = import_file.media_type; "//# sourceMappingURL=data:application/json;base64,",
media_type == MediaType::TypeScript );
|| media_type == MediaType::TSX let decoded_map = base64::decode(input)
|| media_type == MediaType::JSX .expect("Unable to decode source map from emitted file.");
}) Some(decoded_map)
} else {
None
}
} else {
None
}
}
} else {
None
}
} else { } else {
false None
} }
}) }
}
// Compilation happens if either: fn get_source_line(
// - `checkJs` is set to true in TS config &self,
// - entry point is a TS file file_name: &str,
// - any dependency in module graph is a TS file line_number: usize,
fn needs_compilation( ) -> Option<String> {
compile_js: bool, if let Ok(specifier) = ModuleSpecifier::resolve_url(file_name) {
media_type: MediaType, self
module_graph_files: &[&ModuleGraphFile], .file_fetcher
) -> bool { .fetch_cached_source_file(&specifier, Permissions::allow_all())
let mut needs_compilation = match media_type { .map(|out| {
MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true, // Do NOT use .lines(): it skips the terminating empty line.
MediaType::JavaScript => compile_js, // (due to internally using .split_terminator() instead of .split())
_ => false, let lines: Vec<&str> = out.source_code.split('\n').collect();
}; assert!(lines.len() > line_number);
lines[line_number].to_string()
needs_compilation |= module_graph_files.iter().any(|module_file| { })
let media_type = module_file.media_type; } else {
None
media_type == (MediaType::TypeScript) }
|| media_type == (MediaType::TSX) }
|| media_type == (MediaType::JSX)
});
needs_compilation
} }
#[test] #[test]
@ -337,203 +349,3 @@ fn thread_safe() {
fn f<S: Send + Sync>(_: S) {} fn f<S: Send + Sync>(_: S) {}
f(ProgramState::mock(vec![], None)); 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(),
},
],
));
}

View file

@ -6,8 +6,9 @@ use deno_core::error::JsError as CoreJsError;
use sourcemap::SourceMap; use sourcemap::SourceMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::str; use std::str;
use std::sync::Arc;
pub trait SourceMapGetter { pub trait SourceMapGetter: Sync + Send {
/// Returns the raw source map file. /// Returns the raw source map file.
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>>; fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>>;
fn get_source_line( fn get_source_line(
@ -26,7 +27,7 @@ pub type CachedMaps = HashMap<String, Option<SourceMap>>;
/// source, rather than the transpiled source code. /// source, rather than the transpiled source code.
pub fn apply_source_map<G: SourceMapGetter>( pub fn apply_source_map<G: SourceMapGetter>(
js_error: &CoreJsError, js_error: &CoreJsError,
getter: &G, getter: Arc<G>,
) -> CoreJsError { ) -> CoreJsError {
// Note that js_error.frames has already been source mapped in // Note that js_error.frames has already been source mapped in
// prepareStackTrace(). // prepareStackTrace().
@ -39,7 +40,7 @@ pub fn apply_source_map<G: SourceMapGetter>(
// start_column is 0-based, we need 1-based here. // start_column is 0-based, we need 1-based here.
js_error.start_column.map(|n| n + 1), js_error.start_column.map(|n| n + 1),
&mut mappings_map, &mut mappings_map,
getter, getter.clone(),
); );
let start_column = start_column.map(|n| n - 1); let start_column = start_column.map(|n| n - 1);
// It is better to just move end_column to be the same distance away from // 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>, line_number: Option<i64>,
column_number: Option<i64>, column_number: Option<i64>,
mappings_map: &mut CachedMaps, mappings_map: &mut CachedMaps,
getter: &G, getter: Arc<G>,
) -> (Option<String>, Option<i64>, Option<i64>) { ) -> (Option<String>, Option<i64>, Option<i64>) {
match (file_name, line_number, column_number) { match (file_name, line_number, column_number) {
(Some(file_name_v), Some(line_v), Some(column_v)) => { (Some(file_name_v), Some(line_v), Some(column_v)) => {
@ -104,7 +105,7 @@ pub fn get_orig_position<G: SourceMapGetter>(
line_number: i64, line_number: i64,
column_number: i64, column_number: i64,
mappings_map: &mut CachedMaps, mappings_map: &mut CachedMaps,
getter: &G, getter: Arc<G>,
) -> (String, i64, i64) { ) -> (String, i64, i64) {
let maybe_source_map = get_mappings(&file_name, mappings_map, getter); let maybe_source_map = get_mappings(&file_name, mappings_map, getter);
let default_pos = (file_name, line_number, column_number); 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>( fn get_mappings<'a, G: SourceMapGetter>(
file_name: &str, file_name: &str,
mappings_map: &'a mut CachedMaps, mappings_map: &'a mut CachedMaps,
getter: &G, getter: Arc<G>,
) -> &'a Option<SourceMap> { ) -> &'a Option<SourceMap> {
mappings_map mappings_map
.entry(file_name.to_string()) .entry(file_name.to_string())
@ -145,7 +146,7 @@ fn get_mappings<'a, G: SourceMapGetter>(
// the module meta data. // the module meta data.
fn parse_map_string<G: SourceMapGetter>( fn parse_map_string<G: SourceMapGetter>(
file_name: &str, file_name: &str,
getter: &G, getter: Arc<G>,
) -> Option<SourceMap> { ) -> Option<SourceMap> {
getter getter
.get_source_map(file_name) .get_source_map(file_name)
@ -207,8 +208,8 @@ mod tests {
frames: vec![], frames: vec![],
stack: None, stack: None,
}; };
let getter = MockSourceMapGetter {}; let getter = Arc::new(MockSourceMapGetter {});
let actual = apply_source_map(&e, &getter); let actual = apply_source_map(&e, getter);
assert_eq!(actual.source_line, Some("console.log('foo');".to_string())); assert_eq!(actual.source_line, Some("console.log('foo');".to_string()));
} }
} }

View file

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::ast::Location;
use crate::deno_dir::DenoDir; use crate::deno_dir::DenoDir;
use crate::disk_cache::DiskCache; use crate::disk_cache::DiskCache;
use crate::file_fetcher::SourceFileFetcher; use crate::file_fetcher::SourceFileFetcher;
@ -25,8 +26,29 @@ pub type DependencyMap = HashMap<String, Dependency>;
pub type FetchFuture = pub type FetchFuture =
Pin<Box<(dyn Future<Output = Result<CachedModule, AnyError>> + 'static)>>; 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)] #[derive(Debug, Clone)]
pub struct CachedModule { pub struct CachedModule {
pub is_remote: bool,
pub maybe_dependencies: Option<DependencyMap>, pub maybe_dependencies: Option<DependencyMap>,
pub maybe_emit: Option<Emit>, pub maybe_emit: Option<Emit>,
pub maybe_emit_path: Option<(PathBuf, Option<PathBuf>)>, pub maybe_emit_path: Option<(PathBuf, Option<PathBuf>)>,
@ -44,6 +66,7 @@ impl Default for CachedModule {
fn default() -> Self { fn default() -> Self {
let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap(); let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap();
CachedModule { CachedModule {
is_remote: false,
maybe_dependencies: None, maybe_dependencies: None,
maybe_emit: None, maybe_emit: None,
maybe_emit_path: None, maybe_emit_path: None,
@ -76,8 +99,12 @@ impl Default for Emit {
} }
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct Dependency { 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 /// The module specifier that resolves to the runtime code dependency for the
/// module. /// module.
pub maybe_code: Option<ModuleSpecifier>, pub maybe_code: Option<ModuleSpecifier>,
@ -86,17 +113,33 @@ pub struct Dependency {
pub maybe_type: Option<ModuleSpecifier>, 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 { pub trait SpecifierHandler {
/// Instructs the handler to fetch a specifier or retrieve its value from the /// Instructs the handler to fetch a specifier or retrieve its value from the
/// cache. /// 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. /// 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 /// 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 /// 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 /// a root module is identified. The `emit_type` also indicates what form
/// of the module the build info is valid for. /// of the module the build info is valid for.
fn get_ts_build_info( fn get_tsbuildinfo(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError>; ) -> Result<Option<String>, AnyError>;
@ -117,10 +160,10 @@ pub trait SpecifierHandler {
) -> Result<(), AnyError>; ) -> Result<(), AnyError>;
/// Set the build info for a module specifier, also providing the cache type. /// Set the build info for a module specifier, also providing the cache type.
fn set_ts_build_info( fn set_tsbuildinfo(
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
ts_build_info: String, tsbuildinfo: String,
) -> Result<(), AnyError>; ) -> Result<(), AnyError>;
/// Set the graph dependencies for a given module specifier. /// 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 /// existing `file_fetcher` interface, which will eventually be refactored to
/// align it more to the `SpecifierHandler` trait. /// align it more to the `SpecifierHandler` trait.
pub struct FetchHandler { pub struct FetchHandler {
/// An instance of disk where generated (emitted) files are stored.
disk_cache: DiskCache, 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, file_fetcher: SourceFileFetcher,
permissions: Permissions,
} }
impl FetchHandler { impl FetchHandler {
pub fn new( pub fn new(
program_state: &Arc<ProgramState>, program_state: &Arc<ProgramState>,
permissions: Permissions, dynamic_permissions: Permissions,
) -> Result<Self, AnyError> { ) -> Result<Self, AnyError> {
let custom_root = env::var("DENO_DIR").map(String::into).ok(); let custom_root = env::var("DENO_DIR").map(String::into).ok();
let deno_dir = DenoDir::new(custom_root)?; let deno_dir = DenoDir::new(custom_root)?;
@ -187,23 +233,54 @@ impl FetchHandler {
Ok(FetchHandler { Ok(FetchHandler {
disk_cache, disk_cache,
dynamic_permissions,
file_fetcher, file_fetcher,
permissions,
}) })
} }
} }
impl SpecifierHandler for FetchHandler { impl SpecifierHandler for FetchHandler {
fn fetch(&mut self, requested_specifier: ModuleSpecifier) -> FetchFuture { fn fetch(
let permissions = self.permissions.clone(); &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 file_fetcher = self.file_fetcher.clone();
let disk_cache = self.disk_cache.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 { async move {
let source_file = file_fetcher let source_file = file_fetcher
.fetch_source_file(&requested_specifier, None, permissions) .fetch_source_file(&requested_specifier, maybe_referrer, permissions)
.await?; .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 url = source_file.url.clone();
let is_remote = url.scheme() != "file";
let filename = disk_cache.get_cache_filename_with_extension(&url, "meta"); let filename = disk_cache.get_cache_filename_with_extension(&url, "meta");
let maybe_version = if let Ok(bytes) = disk_cache.get(&filename) { let maybe_version = if let Ok(bytes) = disk_cache.get(&filename) {
if let Ok(compiled_file_metadata) = if let Ok(compiled_file_metadata) =
@ -237,6 +314,7 @@ impl SpecifierHandler for FetchHandler {
let specifier = ModuleSpecifier::from(url); let specifier = ModuleSpecifier::from(url);
Ok(CachedModule { Ok(CachedModule {
is_remote,
maybe_dependencies: None, maybe_dependencies: None,
maybe_emit, maybe_emit,
maybe_emit_path, maybe_emit_path,
@ -252,31 +330,32 @@ impl SpecifierHandler for FetchHandler {
.boxed_local() .boxed_local()
} }
fn get_ts_build_info( fn get_tsbuildinfo(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Result<Option<String>, AnyError> { ) -> Result<Option<String>, AnyError> {
let filename = self let filename = self
.disk_cache .disk_cache
.get_cache_filename_with_extension(specifier.as_url(), "buildinfo"); .get_cache_filename_with_extension(specifier.as_url(), "buildinfo");
if let Ok(ts_build_info) = self.disk_cache.get(&filename) { if let Ok(tsbuildinfo) = self.disk_cache.get(&filename) {
return Ok(Some(String::from_utf8(ts_build_info)?)); Ok(Some(String::from_utf8(tsbuildinfo)?))
} else {
Ok(None)
} }
Ok(None)
} }
fn set_ts_build_info( fn set_tsbuildinfo(
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
ts_build_info: String, tsbuildinfo: String,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let filename = self let filename = self
.disk_cache .disk_cache
.get_cache_filename_with_extension(specifier.as_url(), "buildinfo"); .get_cache_filename_with_extension(specifier.as_url(), "buildinfo");
debug!("set_tsbuildinfo - filename {:?}", filename);
self self
.disk_cache .disk_cache
.set(&filename, ts_build_info.as_bytes()) .set(&filename, tsbuildinfo.as_bytes())
.map_err(|e| e.into()) .map_err(|e| e.into())
} }
@ -366,8 +445,8 @@ pub mod tests {
let fetch_handler = FetchHandler { let fetch_handler = FetchHandler {
disk_cache, disk_cache,
dynamic_permissions: Permissions::default(),
file_fetcher, file_fetcher,
permissions: Permissions::allow_all(),
}; };
(temp_dir, fetch_handler) (temp_dir, fetch_handler)
@ -381,8 +460,10 @@ pub mod tests {
"http://localhost:4545/cli/tests/subdir/mod2.ts", "http://localhost:4545/cli/tests/subdir/mod2.ts",
) )
.unwrap(); .unwrap();
let cached_module: CachedModule = let cached_module: CachedModule = file_fetcher
file_fetcher.fetch(specifier.clone()).await.unwrap(); .fetch(specifier.clone(), None, false)
.await
.unwrap();
assert!(cached_module.maybe_emit.is_none()); assert!(cached_module.maybe_emit.is_none());
assert!(cached_module.maybe_dependencies.is_none()); assert!(cached_module.maybe_dependencies.is_none());
assert_eq!(cached_module.media_type, MediaType::TypeScript); assert_eq!(cached_module.media_type, MediaType::TypeScript);
@ -401,18 +482,43 @@ pub mod tests {
"http://localhost:4545/cli/tests/subdir/mod2.ts", "http://localhost:4545/cli/tests/subdir/mod2.ts",
) )
.unwrap(); .unwrap();
let cached_module: CachedModule = let cached_module: CachedModule = file_fetcher
file_fetcher.fetch(specifier.clone()).await.unwrap(); .fetch(specifier.clone(), None, false)
.await
.unwrap();
assert!(cached_module.maybe_emit.is_none()); assert!(cached_module.maybe_emit.is_none());
let code = String::from("some code"); let code = String::from("some code");
file_fetcher file_fetcher
.set_cache(&specifier, &Emit::Cli((code, None))) .set_cache(&specifier, &Emit::Cli((code, None)))
.expect("could not set cache"); .expect("could not set cache");
let cached_module: CachedModule = let cached_module: CachedModule = file_fetcher
file_fetcher.fetch(specifier.clone()).await.unwrap(); .fetch(specifier.clone(), None, false)
.await
.unwrap();
assert_eq!( assert_eq!(
cached_module.maybe_emit, cached_module.maybe_emit,
Some(Emit::Cli(("some code".to_string(), None))) 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);
}
} }

View file

@ -1,3 +1,5 @@
[WILDCARD] [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] [WILDCARD]

2
cli/tests/023_no_ext Normal file
View file

@ -0,0 +1,2 @@
import * as mod4 from "./subdir/mod4.js";
console.log(mod4.isMod4);

1
cli/tests/023_no_ext.out Normal file
View file

@ -0,0 +1 @@
true

View file

@ -1 +0,0 @@
console.log("HELLO");

View file

@ -1 +0,0 @@
HELLO

View file

@ -12,12 +12,12 @@ const lib = function() {
}; };
}(); }();
const c = function() { const c = function() {
const c1; const c1 = [];
return { return {
c: c1 c: c1
}; };
}(); }();
const mod; const mod = [];
return { return {
mod mod
}; };

View file

@ -1,5 +1,17 @@
const map = new Map<string, { foo: string }>(); /* eslint-disable */
function b() {
if (map.get("bar").foo) { return function (
console.log("here"); _target: any,
_propertyKey: string,
_descriptor: PropertyDescriptor,
) {
console.log("b");
};
}
class A {
@b()
a() {
console.log("a");
}
} }

View file

@ -1,7 +1,7 @@
[WILDCARD]Unsupported compiler options in "[WILDCARD]config.tsconfig.json". [WILDCARD]Unsupported compiler options in "[WILDCARD]config.tsconfig.json".
The following options were ignored: The following options were ignored:
module, target module, target
error: TS2532 [ERROR]: Object is possibly 'undefined'. 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.
if (map.get("bar").foo) { a() {
~~~~~~~~~~~~~~ ^
at [WILDCARD]tests/config.ts:3:5 at file:///[WILDCARD]cli/tests/config.ts:[WILDCARD]

View file

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": false,
"module": "amd", "module": "amd",
"strict": true,
"target": "es5" "target": "es5"
} }
} }

View file

@ -1,2 +1,3 @@
error: Modules loaded over https:// are not allowed to import modules over http:// error: Modules imported via https are not allowed to import http modules.
Imported from "https://localhost:5545/cli/tests/disallow_http_from_https.js:2" Importing: http://localhost:4545/cli/tests/001_hello.js
at https://localhost:5545/cli/tests/disallow_http_from_https.js:2:0

View file

@ -1,2 +1,3 @@
error: Modules loaded over https:// are not allowed to import modules over http:// error: Modules imported via https are not allowed to import http modules.
Imported from "https://localhost:5545/cli/tests/disallow_http_from_https.ts:2" Importing: http://localhost:4545/cli/tests/001_hello.js
at https://localhost:5545/cli/tests/disallow_http_from_https.ts:2:0

View file

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

View file

@ -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"

View file

@ -1,2 +1,2 @@
[WILDCARD]error: Cannot resolve module "[WILDCARD]/non-existent" from "[WILDCARD]/error_006_import_ext_failure.ts" [WILDCARD]error: Cannot 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

View file

@ -1,2 +1,4 @@
// eslint-disable-next-line // eslint-disable-next-line
import * as badModule from "bad-module.ts"; import * as badModule from "bad-module.ts";
console.log(badModule);

View file

@ -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

View file

@ -1,3 +1,4 @@
[WILDCARD] [WILDCARD]
error: Uncaught TypeError: read access to "[WILDCARD]passwd", run again with the --allow-read flag error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
Imported from "[WILDCARD]evil_remote_import.js:3" Importing: file:///c:/etc/passwd
at http://localhost:4545/cli/tests/subdir/evil_remote_import.js:3:0

View file

@ -1,3 +1,4 @@
[WILDCARD] [WILDCARD]
error: Remote modules are not allowed to statically import local modules. Use dynamic import instead. error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
Imported from "[WILDCARD]error_local_static_import_from_remote.js:1" Importing: file:///some/dir/file.js
at http://localhost:4545/cli/tests/error_local_static_import_from_remote.js:1:0

View file

@ -1,3 +1,4 @@
[WILDCARD] [WILDCARD]
error: Remote modules are not allowed to statically import local modules. Use dynamic import instead. error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead.
Imported from "[WILDCARD]error_local_static_import_from_remote.ts:1" Importing: file:///some/dir/file.ts
at http://localhost:4545/cli/tests/error_local_static_import_from_remote.ts:1:0

View file

@ -0,0 +1,3 @@
import clone from "https://jspm.dev/lodash@4/clone";
console.log(clone);

View file

@ -0,0 +1 @@
[Function: clone]

View file

@ -1807,9 +1807,9 @@ itest!(_022_info_flag_script {
http_server: true, http_server: true,
}); });
itest!(_023_no_ext_with_headers { itest!(_023_no_ext {
args: "run --reload 023_no_ext_with_headers", args: "run --reload 023_no_ext",
output: "023_no_ext_with_headers.out", output: "023_no_ext.out",
}); });
// TODO(lucacasonato): remove --unstable when permissions goes stable // TODO(lucacasonato): remove --unstable when permissions goes stable
@ -2018,7 +2018,7 @@ itest!(_044_bad_resource {
}); });
itest!(_045_proxy { 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", output: "045_proxy_test.ts.out",
http_server: true, http_server: true,
}); });
@ -2764,6 +2764,11 @@ itest!(tsx_imports {
output: "tsx_imports.ts.out", 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 { itest!(fix_js_import_js {
args: "run --quiet --reload fix_js_import_js.ts", args: "run --quiet --reload fix_js_import_js.ts",
output: "fix_js_import_js.ts.out", output: "fix_js_import_js.ts.out",

View file

@ -1,2 +1,3 @@
[WILDCARD]Subresource integrity check failed --lock=lock_check_err.json [WILDCARD]The source code is invalid, as it does not match the expected hash in the lock file.
http://127.0.0.1:4545/cli/tests/003_relative_import.ts Specifier: http://127.0.0.1:4545/cli/tests/003_relative_import.ts
Lock file: lock_check_err.json

View file

@ -1,2 +1,3 @@
[WILDCARD]Subresource integrity check failed --lock=lock_check_err2.json [WILDCARD]The source code is invalid, as it does not match the expected hash in the lock file.
http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js Specifier: http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js
Lock file: lock_check_err2.json

View file

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

View file

@ -0,0 +1,3 @@
import * as config from "./some.json";
console.log(config);

View file

@ -0,0 +1,5 @@
{
"config": {
"debug": true
}
}

View file

@ -1,14 +1,16 @@
[WILDCARD] [WILDCARD]
DEBUG RS - [WILDCARD] - Files: [WILDCARD] DEBUG RS - [WILDCARD] - Compilation statistics:
DEBUG RS - [WILDCARD] - Nodes: [WILDCARD] Files: [WILDCARD]
DEBUG RS - [WILDCARD] - Identifiers: [WILDCARD] Nodes: [WILDCARD]
DEBUG RS - [WILDCARD] - Symbols: [WILDCARD] Identifiers: [WILDCARD]
DEBUG RS - [WILDCARD] - Types: [WILDCARD] Symbols: [WILDCARD]
DEBUG RS - [WILDCARD] - Instantiations: [WILDCARD] Types: [WILDCARD]
DEBUG RS - [WILDCARD] - Parse time: [WILDCARD] Instantiations: [WILDCARD]
DEBUG RS - [WILDCARD] - Bind time: [WILDCARD] Parse time: [WILDCARD]
DEBUG RS - [WILDCARD] - Check time: [WILDCARD] Bind time: [WILDCARD]
DEBUG RS - [WILDCARD] - Emit time: [WILDCARD] Check time: [WILDCARD]
DEBUG RS - [WILDCARD] - Total TS time: [WILDCARD] Emit time: [WILDCARD]
DEBUG RS - [WILDCARD] - Compile time: [WILDCARD] Total TS time: [WILDCARD]
Compile time: [WILDCARD]
[WILDCARD] [WILDCARD]

View file

@ -1,5 +1,4 @@
Check [WILDCARD]single_compile_with_reload.ts Check [WILDCARD]single_compile_with_reload.ts
Check [WILDCARD]single_compile_with_reload_dyn.ts
Hello Hello
1 1
2 2

View file

@ -1,4 +1,4 @@
Check [WILDCARD]ts_type_only_import.ts Check [WILDCARD]ts_type_only_import.ts
Warning Failed to get compiled source code of "[WILDCARD]ts_type_only_import.d.ts". warning: Compiled module not found "[WILDCARD]ts_type_only_import.d.ts"
Reason: [WILDCARD] (os error 2) From: [WILDCARD]ts_type_only_import.ts
If the source file provides only type exports, prefer to use "import type" or "export type" syntax instead. If the source module contains only types, use `import type` and `export type` to import it instead.

View file

@ -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", "http",
"https", "https",
"file", "file",

View file

@ -2,7 +2,6 @@
use crate::ast::parse; use crate::ast::parse;
use crate::ast::Location; use crate::ast::Location;
use crate::colors;
use crate::diagnostics::Diagnostics; use crate::diagnostics::Diagnostics;
use crate::disk_cache::DiskCache; use crate::disk_cache::DiskCache;
use crate::file_fetcher::SourceFile; use crate::file_fetcher::SourceFile;
@ -14,7 +13,6 @@ use crate::module_graph::ModuleGraph;
use crate::module_graph::ModuleGraphLoader; use crate::module_graph::ModuleGraphLoader;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::program_state::ProgramState; use crate::program_state::ProgramState;
use crate::source_maps::SourceMapGetter;
use crate::tsc_config; use crate::tsc_config;
use crate::version; use crate::version;
use deno_core::error::generic_error; use deno_core::error::generic_error;
@ -29,7 +27,6 @@ use deno_core::JsRuntime;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::RuntimeOptions; use deno_core::RuntimeOptions;
use log::debug; use log::debug;
use log::info;
use log::Level; use log::Level;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
@ -231,12 +228,6 @@ pub struct CompiledFileMetadata {
} }
impl 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> { pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self) serde_json::to_string(self)
} }
@ -308,15 +299,6 @@ struct BundleResponse {
stats: Option<Vec<Stat>>, 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 // TODO(bartlomieju): possible deduplicate once TS refactor is stabilized
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -360,197 +342,6 @@ impl TsCompiler {
c.insert(url.clone()); 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 /// For a given module, generate a single file JavaScript output that includes
/// all the dependencies for that module. /// all the dependencies for that module.
pub async fn bundle( pub async fn bundle(
@ -666,39 +457,6 @@ impl TsCompiler {
Ok(output) 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( fn cache_emitted_files(
&self, &self,
emit_map: HashMap<String, EmittedSource>, emit_map: HashMap<String, EmittedSource>,
@ -730,45 +488,6 @@ impl TsCompiler {
Ok(()) 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. /// Save compiled JS file for given TS module to on-disk cache.
/// ///
/// Along compiled file a special metadata file is saved as well containing /// 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. /// Save source map file for given TS module to on-disk cache.
fn cache_source_map( fn cache_source_map(
&self, &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)] #[derive(Debug, Deserialize)]
struct CreateHashArgs { struct CreateHashArgs {
data: String, data: String,
@ -1425,7 +1034,6 @@ fn parse_deno_types(comment: &str) -> Option<String> {
#[repr(i32)] #[repr(i32)]
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub enum CompilerRequestType { pub enum CompilerRequestType {
Compile = 0,
Bundle = 1, Bundle = 1,
RuntimeCompile = 2, RuntimeCompile = 2,
RuntimeBundle = 3, RuntimeBundle = 3,
@ -1438,7 +1046,6 @@ impl Serialize for CompilerRequestType {
S: Serializer, S: Serializer,
{ {
let value: i32 = match self { let value: i32 = match self {
CompilerRequestType::Compile => 0 as i32,
CompilerRequestType::Bundle => 1 as i32, CompilerRequestType::Bundle => 1 as i32,
CompilerRequestType::RuntimeCompile => 2 as i32, CompilerRequestType::RuntimeCompile => 2 as i32,
CompilerRequestType::RuntimeBundle => 3 as i32, CompilerRequestType::RuntimeBundle => 3 as i32,
@ -1451,12 +1058,8 @@ impl Serialize for CompilerRequestType {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::deno_dir;
use crate::fs as deno_fs; use crate::fs as deno_fs;
use crate::http_cache;
use crate::program_state::ProgramState; use crate::program_state::ProgramState;
use deno_core::ModuleSpecifier;
use std::path::PathBuf;
use tempfile::TempDir; use tempfile::TempDir;
#[test] #[test]
@ -1516,75 +1119,6 @@ mod tests {
assert!(parse_ts_reference(r#"/ <asset path="./styles.css" />"#).is_none()); assert!(parse_ts_reference(r#"/ <asset path="./styles.css" />"#).is_none());
} }
#[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] #[tokio::test]
async fn test_bundle() { async fn test_bundle() {
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))

View file

@ -163,8 +163,9 @@ delete Object.prototype.__proto__;
4: "TSX", 4: "TSX",
5: "Json", 5: "Json",
6: "Wasm", 6: "Wasm",
7: "BuildInfo", 7: "TsBuildInfo",
8: "Unknown", 8: "SourceMap",
9: "Unknown",
JavaScript: 0, JavaScript: 0,
JSX: 1, JSX: 1,
TypeScript: 2, TypeScript: 2,
@ -172,8 +173,9 @@ delete Object.prototype.__proto__;
TSX: 4, TSX: 4,
Json: 5, Json: 5,
Wasm: 6, Wasm: 6,
BuildInfo: 7, TsBuildInfo: 7,
Unknown: 6, SourceMap: 8,
Unknown: 9,
}; };
function getExtension(fileName, mediaType) { function getExtension(fileName, mediaType) {
@ -183,7 +185,9 @@ delete Object.prototype.__proto__;
case MediaType.JSX: case MediaType.JSX:
return ts.Extension.Jsx; return ts.Extension.Jsx;
case MediaType.TypeScript: 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: case MediaType.TSX:
return ts.Extension.Tsx; return ts.Extension.Tsx;
case MediaType.Wasm: case MediaType.Wasm:
@ -366,7 +370,7 @@ delete Object.prototype.__proto__;
} }
/** @type {{ data: string; hash: string; }} */ /** @type {{ data: string; hash: string; }} */
const { data, hash } = core.jsonOpSync( const { data, hash, scriptKind } = core.jsonOpSync(
"op_load", "op_load",
{ specifier }, { specifier },
); );
@ -375,6 +379,8 @@ delete Object.prototype.__proto__;
specifier, specifier,
data, data,
languageVersion, languageVersion,
false,
scriptKind,
); );
sourceFile.moduleName = specifier; sourceFile.moduleName = specifier;
sourceFile.version = hash; sourceFile.version = hash;
@ -406,7 +412,6 @@ delete Object.prototype.__proto__;
let maybeSpecifiers; let maybeSpecifiers;
if (sourceFiles) { if (sourceFiles) {
maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName); maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName);
debug(` specifiers: ${maybeSpecifiers.join(", ")}`);
} }
return core.jsonOpSync( return core.jsonOpSync(
"op_emit", "op_emit",
@ -465,11 +470,12 @@ delete Object.prototype.__proto__;
specifiers, specifiers,
base, base,
}); });
return resolved.map(([resolvedFileName, extension]) => ({ let r = resolved.map(([resolvedFileName, extension]) => ({
resolvedFileName, resolvedFileName,
extension, extension,
isExternalLibraryImport: false, isExternalLibraryImport: false,
})); }));
return r;
} }
}, },
createHash(data) { createHash(data) {
@ -649,7 +655,6 @@ delete Object.prototype.__proto__;
// Warning! The values in this enum are duplicated in `cli/msg.rs` // Warning! The values in this enum are duplicated in `cli/msg.rs`
// Update carefully! // Update carefully!
const CompilerRequestType = { const CompilerRequestType = {
Compile: 0,
Bundle: 1, Bundle: 1,
RuntimeCompile: 2, RuntimeCompile: 2,
RuntimeBundle: 3, 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) { function createRuntimeCompileWriteFile(state) {
return function writeFile(fileName, data, sourceFiles) { return function writeFile(fileName, data, sourceFiles) {
assert(sourceFiles); assert(sourceFiles);
@ -959,101 +945,6 @@ delete Object.prototype.__proto__;
.map((sym) => sym.getName()); .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({ function bundle({
compilerOptions, compilerOptions,
rootNames, rootNames,
@ -1296,11 +1187,6 @@ delete Object.prototype.__proto__;
function tsCompilerOnMessage(msg) { function tsCompilerOnMessage(msg) {
const request = msg.data; const request = msg.data;
switch (request.type) { switch (request.type) {
case CompilerRequestType.Compile: {
const result = compile(request);
opCompilerRespond(result);
break;
}
case CompilerRequestType.Bundle: { case CompilerRequestType.Bundle: {
const result = bundle(request); const result = bundle(request);
opCompilerRespond(result); opCompilerRespond(result);

View file

@ -21,6 +21,7 @@ use deno_core::RuntimeOptions;
use deno_core::Snapshot; use deno_core::Snapshot;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug, Clone, Default, Eq, PartialEq)] #[derive(Debug, Clone, Default, Eq, PartialEq)]
@ -40,7 +41,7 @@ pub struct Request {
/// Indicates to the tsc runtime if debug logging should occur. /// Indicates to the tsc runtime if debug logging should occur.
pub debug: bool, pub debug: bool,
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub graph: Rc<Graph2>, pub graph: Rc<RefCell<Graph2>>,
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub hash_data: Vec<Vec<u8>>, pub hash_data: Vec<Vec<u8>>,
#[serde(skip_serializing)] #[serde(skip_serializing)]
@ -65,14 +66,14 @@ pub struct Response {
struct State { struct State {
hash_data: Vec<Vec<u8>>, hash_data: Vec<Vec<u8>>,
emitted_files: Vec<EmittedFile>, emitted_files: Vec<EmittedFile>,
graph: Rc<Graph2>, graph: Rc<RefCell<Graph2>>,
maybe_tsbuildinfo: Option<String>, maybe_tsbuildinfo: Option<String>,
maybe_response: Option<RespondArgs>, maybe_response: Option<RespondArgs>,
} }
impl State { impl State {
pub fn new( pub fn new(
graph: Rc<Graph2>, graph: Rc<RefCell<Graph2>>,
hash_data: Vec<Vec<u8>>, hash_data: Vec<Vec<u8>>,
maybe_tsbuildinfo: Option<String>, maybe_tsbuildinfo: Option<String>,
) -> Self { ) -> Self {
@ -162,10 +163,23 @@ fn load(state: &mut State, args: Value) -> Result<Value, AnyError> {
let specifier = ModuleSpecifier::resolve_url_or_path(&v.specifier) let specifier = ModuleSpecifier::resolve_url_or_path(&v.specifier)
.context("Error converting a string module specifier for \"op_load\".")?; .context("Error converting a string module specifier for \"op_load\".")?;
let mut hash: Option<String> = None; let mut hash: Option<String> = None;
let mut media_type = MediaType::Unknown;
let data = if &v.specifier == "deno:///.tsbuildinfo" { let data = if &v.specifier == "deno:///.tsbuildinfo" {
state.maybe_tsbuildinfo.clone() 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 { } 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 { if let Some(source) = &maybe_source {
let mut data = vec![source.as_bytes().to_owned()]; let mut data = vec![source.as_bytes().to_owned()];
data.extend_from_slice(&state.hash_data); data.extend_from_slice(&state.hash_data);
@ -174,7 +188,9 @@ fn load(state: &mut State, args: Value) -> Result<Value, AnyError> {
maybe_source maybe_source
}; };
Ok(json!({ "data": data, "hash": hash })) Ok(
json!({ "data": data, "hash": hash, "scriptKind": media_type.as_ts_script_kind() }),
)
} }
#[derive(Debug, Deserialize)] #[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(), MediaType::from(specifier).as_ts_extension().to_string(),
)); ));
} else { } else {
let resolved_specifier = state.graph.resolve(specifier, &referrer)?; let graph = state.graph.borrow();
let media_type = if let Some(media_type) = match graph.resolve(specifier, &referrer, true) {
state.graph.get_media_type(&resolved_specifier) Ok(resolved_specifier) => {
{ let media_type = if let Some(media_type) =
media_type graph.get_media_type(&resolved_specifier)
} else { {
bail!( media_type
"Unable to resolve media type for specifier: \"{}\"", } else {
resolved_specifier bail!(
) "Unable to resolve media type for specifier: \"{}\"",
}; resolved_specifier
resolved )
.push((resolved_specifier.to_string(), media_type.as_ts_extension())); };
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)] #[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct RespondArgs { struct RespondArgs {
pub diagnostics: Diagnostics, pub diagnostics: Diagnostics,
pub stats: Stats, pub stats: Stats,
} }
@ -269,9 +297,7 @@ pub fn exec(
runtime runtime
.execute("[native code]", startup_source) .execute("[native code]", startup_source)
.context("Could not properly start the compiler runtime.")?; .context("Could not properly start the compiler runtime.")?;
runtime runtime.execute("[native_code]", &exec_source)?;
.execute("[native_code]", &exec_source)
.context("Execute request failed.")?;
let op_state = runtime.op_state(); let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut(); let mut op_state = op_state.borrow_mut();
@ -324,10 +350,10 @@ mod tests {
})); }));
let mut builder = GraphBuilder2::new(handler.clone(), None); let mut builder = GraphBuilder2::new(handler.clone(), None);
builder builder
.insert(&specifier) .add(&specifier, false)
.await .await
.expect("module not inserted"); .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) State::new(graph, hash_data, maybe_tsbuildinfo)
} }
@ -410,7 +436,8 @@ mod tests {
actual, actual,
json!({ json!({
"data": "console.log(\"hello deno\");\n", "data": "console.log(\"hello deno\");\n",
"hash": "149c777056afcc973d5fcbe11421b6d5ddc57b81786765302030d7fc893bf729" "hash": "149c777056afcc973d5fcbe11421b6d5ddc57b81786765302030d7fc893bf729",
"scriptKind": 3,
}) })
); );
} }
@ -433,7 +460,8 @@ mod tests {
actual, actual,
json!({ json!({
"data": "some content", "data": "some content",
"hash": null "hash": null,
"scriptKind": 0,
}) })
); );
} }
@ -451,6 +479,7 @@ mod tests {
json!({ json!({
"data": null, "data": null,
"hash": null, "hash": null,
"scriptKind": 0,
}) })
) )
} }
@ -475,7 +504,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn test_resolve_error() { async fn test_resolve_empty() {
let mut state = setup( let mut state = setup(
Some( Some(
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a.ts") ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a.ts")
@ -485,10 +514,11 @@ mod tests {
None, None,
) )
.await; .await;
resolve( let actual = resolve(
&mut state, &mut state,
json!({ "base": "https://deno.land/x/a.ts", "specifiers": [ "./bad.ts" ]}), 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] #[tokio::test]
@ -544,17 +574,16 @@ mod tests {
})); }));
let mut builder = GraphBuilder2::new(handler.clone(), None); let mut builder = GraphBuilder2::new(handler.clone(), None);
builder builder
.insert(&specifier) .add(&specifier, false)
.await .await
.expect("module not inserted"); .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!({ let config = TsConfig::new(json!({
"allowJs": true, "allowJs": true,
"checkJs": false, "checkJs": false,
"esModuleInterop": true, "esModuleInterop": true,
"emitDecoratorMetadata": false, "emitDecoratorMetadata": false,
"incremental": true, "incremental": true,
"isolatedModules": true,
"jsx": "react", "jsx": "react",
"jsxFactory": "React.createElement", "jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment", "jsxFragmentFactory": "React.Fragment",

View file

@ -214,6 +214,21 @@ impl TsConfig {
self.0.to_string().as_bytes().to_owned() 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 /// Take an optional string representing a user provided TypeScript config file
/// which was passed in via the `--config` compiler option and merge it with /// which was passed in via the `--config` compiler option and merge it with
/// the configuration. Returning the result which optionally contains any /// the configuration. Returning the result which optionally contains any

View file

@ -121,7 +121,7 @@ impl Worker {
module_loader: Some(module_loader), module_loader: Some(module_loader),
startup_snapshot: Some(startup_snapshot), startup_snapshot: Some(startup_snapshot),
js_error_create_fn: Some(Box::new(move |core_js_error| { 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() ..Default::default()
}); });