diff --git a/cli/args/mod.rs b/cli/args/mod.rs index f820afe78d..fd34b53c8c 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1363,9 +1363,9 @@ impl CliOptions { Ok(DenoLintConfig { default_jsx_factory: (!transpile_options.jsx_automatic) - .then(|| transpile_options.jsx_factory.clone()), + .then_some(transpile_options.jsx_factory), default_jsx_fragment_factory: (!transpile_options.jsx_automatic) - .then(|| transpile_options.jsx_fragment_factory.clone()), + .then_some(transpile_options.jsx_fragment_factory), }) } diff --git a/cli/emit.rs b/cli/emit.rs index 3cd23b7abb..733a89d832 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -5,6 +5,7 @@ use crate::cache::FastInsecureHasher; use crate::cache::ParsedSourceCache; use crate::resolver::CjsTracker; +use deno_ast::EmittedSourceText; use deno_ast::ModuleKind; use deno_ast::SourceMapOption; use deno_ast::SourceRange; @@ -132,6 +133,7 @@ impl Emitter { &transpile_and_emit_options.0, &transpile_and_emit_options.1, ) + .map(|r| r.text) } }) .await @@ -166,7 +168,8 @@ impl Emitter { source.clone(), &self.transpile_and_emit_options.0, &self.transpile_and_emit_options.1, - )?; + )? + .text; helper.post_emit_parsed_source( specifier, &transpiled_source, @@ -177,6 +180,31 @@ impl Emitter { } } + pub fn emit_parsed_source_for_deno_compile( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + module_kind: deno_ast::ModuleKind, + source: &Arc, + ) -> Result<(String, String), AnyError> { + let mut emit_options = self.transpile_and_emit_options.1.clone(); + emit_options.inline_sources = false; + emit_options.source_map = SourceMapOption::Separate; + // strip off the path to have more deterministic builds as we don't care + // about the source name because we manually provide the source map to v8 + emit_options.source_map_base = Some(deno_path_util::url_parent(specifier)); + let source = EmitParsedSourceHelper::transpile( + &self.parsed_source_cache, + specifier, + media_type, + module_kind, + source.clone(), + &self.transpile_and_emit_options.0, + &emit_options, + )?; + Ok((source.text, source.source_map.unwrap())) + } + /// Expects a file URL, panics otherwise. pub async fn load_and_emit_for_hmr( &self, @@ -282,7 +310,7 @@ impl<'a> EmitParsedSourceHelper<'a> { source: Arc, transpile_options: &deno_ast::TranspileOptions, emit_options: &deno_ast::EmitOptions, - ) -> Result { + ) -> Result { // nothing else needs the parsed source at this point, so remove from // the cache in order to not transpile owned let parsed_source = parsed_source_cache @@ -302,8 +330,7 @@ impl<'a> EmitParsedSourceHelper<'a> { source } }; - debug_assert!(transpiled_source.source_map.is_none()); - Ok(transpiled_source.text) + Ok(transpiled_source) } pub fn post_emit_parsed_source( diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 2ed52010fb..f7ffa46e4f 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -91,6 +91,7 @@ use super::serialization::DenoCompileModuleData; use super::serialization::DeserializedDataSection; use super::serialization::RemoteModulesStore; use super::serialization::RemoteModulesStoreBuilder; +use super::serialization::SourceMapStore; use super::virtual_fs::output_vfs; use super::virtual_fs::BuiltVfs; use super::virtual_fs::FileBackedVfs; @@ -98,6 +99,7 @@ use super::virtual_fs::VfsBuilder; use super::virtual_fs::VfsFileSubDataKind; use super::virtual_fs::VfsRoot; use super::virtual_fs::VirtualDirectory; +use super::virtual_fs::VirtualDirectoryEntries; use super::virtual_fs::WindowsSystemRootablePath; pub static DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME: &str = @@ -203,18 +205,25 @@ pub struct Metadata { pub otel_config: OtelConfig, } +#[allow(clippy::too_many_arguments)] fn write_binary_bytes( mut file_writer: File, original_bin: Vec, metadata: &Metadata, npm_snapshot: Option, remote_modules: &RemoteModulesStoreBuilder, + source_map_store: &SourceMapStore, vfs: &BuiltVfs, compile_flags: &CompileFlags, ) -> Result<(), AnyError> { - let data_section_bytes = - serialize_binary_data_section(metadata, npm_snapshot, remote_modules, vfs) - .context("Serializing binary data section.")?; + let data_section_bytes = serialize_binary_data_section( + metadata, + npm_snapshot, + remote_modules, + source_map_store, + vfs, + ) + .context("Serializing binary data section.")?; let target = compile_flags.resolve_target(); if target.contains("linux") { @@ -256,6 +265,7 @@ pub struct StandaloneData { pub modules: StandaloneModules, pub npm_snapshot: Option, pub root_path: PathBuf, + pub source_maps: SourceMapStore, pub vfs: Arc, } @@ -283,13 +293,12 @@ impl StandaloneModules { pub fn read<'a>( &'a self, specifier: &'a ModuleSpecifier, + kind: VfsFileSubDataKind, ) -> Result>, AnyError> { if specifier.scheme() == "file" { let path = deno_path_util::url_to_file_path(specifier)?; let bytes = match self.vfs.file_entry(&path) { - Ok(entry) => self - .vfs - .read_file_all(entry, VfsFileSubDataKind::ModuleGraph)?, + Ok(entry) => self.vfs.read_file_all(entry, kind)?, Err(err) if err.kind() == ErrorKind::NotFound => { match RealFs.read_file_sync(&path, None) { Ok(bytes) => bytes, @@ -307,7 +316,18 @@ impl StandaloneModules { data: bytes, })) } else { - self.remote_modules.read(specifier) + self.remote_modules.read(specifier).map(|maybe_entry| { + maybe_entry.map(|entry| DenoCompileModuleData { + media_type: entry.media_type, + specifier: entry.specifier, + data: match kind { + VfsFileSubDataKind::Raw => entry.data, + VfsFileSubDataKind::ModuleGraph => { + entry.transpiled_data.unwrap_or(entry.data) + } + }, + }) + }) } } } @@ -328,7 +348,8 @@ pub fn extract_standalone( mut metadata, npm_snapshot, remote_modules, - mut vfs_dir, + source_maps, + vfs_root_entries, vfs_files_data, } = match deserialize_binary_data_section(data)? { Some(data_section) => data_section, @@ -351,11 +372,12 @@ pub fn extract_standalone( metadata.argv.push(arg.into_string().unwrap()); } let vfs = { - // align the name of the directory with the root dir - vfs_dir.name = root_path.file_name().unwrap().to_string_lossy().to_string(); - let fs_root = VfsRoot { - dir: vfs_dir, + dir: VirtualDirectory { + // align the name of the directory with the root dir + name: root_path.file_name().unwrap().to_string_lossy().to_string(), + entries: vfs_root_entries, + }, root_path: root_path.clone(), start_file_offset: 0, }; @@ -372,6 +394,7 @@ pub fn extract_standalone( }, npm_snapshot, root_path, + source_maps, vfs, })) } @@ -451,7 +474,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { ) } } - self.write_standalone_binary(options, original_binary).await + self.write_standalone_binary(options, original_binary) } async fn get_base_binary( @@ -554,7 +577,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { /// This functions creates a standalone deno binary by appending a bundle /// and magic trailer to the currently executing binary. #[allow(clippy::too_many_arguments)] - async fn write_standalone_binary( + fn write_standalone_binary( &self, options: WriteBinOptions<'_>, original_bin: Vec, @@ -598,71 +621,81 @@ impl<'a> DenoCompileBinaryWriter<'a> { .with_context(|| format!("Including {}", path.display()))?; } let mut remote_modules_store = RemoteModulesStoreBuilder::default(); - let mut code_cache_key_hasher = if self.cli_options.code_cache_enabled() { - Some(FastInsecureHasher::new_deno_versioned()) - } else { - None - }; + let mut source_maps = Vec::with_capacity(graph.specifiers_count()); + // todo(dsherret): transpile in parallel for module in graph.modules() { if module.specifier().scheme() == "data" { continue; // don't store data urls as an entry as they're in the code } - if let Some(hasher) = &mut code_cache_key_hasher { - if let Some(source) = module.source() { - hasher.write(module.specifier().as_str().as_bytes()); - hasher.write(source.as_bytes()); - } - } - let (maybe_source, media_type) = match module { + let (maybe_original_source, maybe_transpiled, media_type) = match module { deno_graph::Module::Js(m) => { - let source = if m.media_type.is_emittable() { + let original_bytes = m.source.as_bytes().to_vec(); + let maybe_transpiled = if m.media_type.is_emittable() { let is_cjs = self.cjs_tracker.is_cjs_with_known_is_script( &m.specifier, m.media_type, m.is_script, )?; let module_kind = ModuleKind::from_is_cjs(is_cjs); - let source = self - .emitter - .emit_parsed_source( + let (source, source_map) = + self.emitter.emit_parsed_source_for_deno_compile( &m.specifier, m.media_type, module_kind, &m.source, - ) - .await?; - source.into_bytes() + )?; + if source != m.source.as_ref() { + source_maps.push((&m.specifier, source_map)); + Some(source.into_bytes()) + } else { + None + } } else { - m.source.as_bytes().to_vec() + None }; - (Some(source), m.media_type) + (Some(original_bytes), maybe_transpiled, m.media_type) } deno_graph::Module::Json(m) => { - (Some(m.source.as_bytes().to_vec()), m.media_type) + (Some(m.source.as_bytes().to_vec()), None, m.media_type) } deno_graph::Module::Wasm(m) => { - (Some(m.source.to_vec()), MediaType::Wasm) + (Some(m.source.to_vec()), None, MediaType::Wasm) } deno_graph::Module::Npm(_) | deno_graph::Module::Node(_) - | deno_graph::Module::External(_) => (None, MediaType::Unknown), + | deno_graph::Module::External(_) => (None, None, MediaType::Unknown), }; - if module.specifier().scheme() == "file" { - let file_path = deno_path_util::url_to_file_path(module.specifier())?; - vfs - .add_file_with_data( - &file_path, - match maybe_source { - Some(source) => source, - None => RealFs.read_file_sync(&file_path, None)?.into_owned(), - }, - VfsFileSubDataKind::ModuleGraph, - ) - .with_context(|| { - format!("Failed adding '{}'", file_path.display()) - })?; - } else if let Some(source) = maybe_source { - remote_modules_store.add(module.specifier(), media_type, source); + if let Some(original_source) = maybe_original_source { + if module.specifier().scheme() == "file" { + let file_path = deno_path_util::url_to_file_path(module.specifier())?; + vfs + .add_file_with_data( + &file_path, + original_source, + VfsFileSubDataKind::Raw, + ) + .with_context(|| { + format!("Failed adding '{}'", file_path.display()) + })?; + if let Some(transpiled_source) = maybe_transpiled { + vfs + .add_file_with_data( + &file_path, + transpiled_source, + VfsFileSubDataKind::ModuleGraph, + ) + .with_context(|| { + format!("Failed adding '{}'", file_path.display()) + })?; + } + } else { + remote_modules_store.add( + module.specifier(), + media_type, + original_source, + maybe_transpiled, + ); + } } } remote_modules_store.add_redirects(&graph.redirects); @@ -695,6 +728,28 @@ impl<'a> DenoCompileBinaryWriter<'a> { None => StandaloneRelativeFileBaseUrl::WindowsSystemRoot, }; + let code_cache_key = if self.cli_options.code_cache_enabled() { + let mut hasher = FastInsecureHasher::new_deno_versioned(); + for module in graph.modules() { + if let Some(source) = module.source() { + hasher + .write(root_dir_url.specifier_key(module.specifier()).as_bytes()); + hasher.write(source.as_bytes()); + } + } + Some(hasher.finish()) + } else { + None + }; + + let mut source_map_store = SourceMapStore::with_capacity(source_maps.len()); + for (specifier, source_map) in source_maps { + source_map_store.add( + Cow::Owned(root_dir_url.specifier_key(specifier).into_owned()), + Cow::Owned(source_map), + ); + } + let node_modules = match self.npm_resolver.as_inner() { InnerCliNpmResolverRef::Managed(_) => { npm_snapshot.as_ref().map(|_| NodeModules::Managed { @@ -742,7 +797,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { let metadata = Metadata { argv: compile_flags.args.clone(), seed: self.cli_options.seed(), - code_cache_key: code_cache_key_hasher.map(|h| h.finish()), + code_cache_key, location: self.cli_options.location_flag().clone(), permissions: self.cli_options.permission_flags().clone(), v8_flags: self.cli_options.v8_flags().clone(), @@ -809,6 +864,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { &metadata, npm_snapshot.map(|s| s.into_serialized()), &remote_modules_store, + &source_map_store, &vfs, compile_flags, ) @@ -903,10 +959,10 @@ impl<'a> DenoCompileBinaryWriter<'a> { root_dir.name = DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME.to_string(); let mut new_entries = Vec::with_capacity(root_dir.entries.len()); let mut localhost_entries = IndexMap::new(); - for entry in std::mem::take(&mut root_dir.entries) { + for entry in root_dir.entries.take_inner() { match entry { - VfsEntry::Dir(dir) => { - for entry in dir.entries { + VfsEntry::Dir(mut dir) => { + for entry in dir.entries.take_inner() { log::debug!("Flattening {} into node_modules", entry.name()); if let Some(existing) = localhost_entries.insert(entry.name().to_string(), entry) @@ -925,11 +981,11 @@ impl<'a> DenoCompileBinaryWriter<'a> { } new_entries.push(VfsEntry::Dir(VirtualDirectory { name: "localhost".to_string(), - entries: localhost_entries.into_iter().map(|(_, v)| v).collect(), + entries: VirtualDirectoryEntries::new( + localhost_entries.into_iter().map(|(_, v)| v).collect(), + ), })); - // needs to be sorted by name - new_entries.sort_by(|a, b| a.name().cmp(b.name())); - root_dir.entries = new_entries; + root_dir.entries = VirtualDirectoryEntries::new(new_entries); // it's better to not expose the user's cache directory, so take it out // of there @@ -937,10 +993,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { let parent_dir = vfs.get_dir_mut(parent).unwrap(); let index = parent_dir .entries - .iter() - .position(|entry| { - entry.name() == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME - }) + .binary_search(DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME) .unwrap(); let npm_global_cache_dir_entry = parent_dir.entries.remove(index); @@ -950,11 +1003,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { Cow::Borrowed(DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME); for ancestor in parent.ancestors() { let dir = vfs.get_dir_mut(ancestor).unwrap(); - if let Some(index) = dir - .entries - .iter() - .position(|entry| entry.name() == last_name) - { + if let Ok(index) = dir.entries.binary_search(&last_name) { dir.entries.remove(index); } last_name = Cow::Owned(dir.name.clone()); @@ -965,7 +1014,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { // now build the vfs and add the global cache dir entry there let mut built_vfs = vfs.build(); - built_vfs.root.insert_entry(npm_global_cache_dir_entry); + built_vfs.entries.insert(npm_global_cache_dir_entry); built_vfs } InnerCliNpmResolverRef::Byonm(_) => vfs.build(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 08ee5ba11a..d7227f2bd5 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -55,6 +55,7 @@ use node_resolver::errors::ClosestPkgJsonError; use node_resolver::NodeResolutionKind; use node_resolver::ResolutionMode; use serialization::DenoCompileModuleSource; +use serialization::SourceMapStore; use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; @@ -122,6 +123,7 @@ struct SharedModuleLoaderState { npm_module_loader: Arc, npm_req_resolver: Arc, npm_resolver: Arc, + source_maps: SourceMapStore, vfs: Arc, workspace_resolver: WorkspaceResolver, } @@ -396,7 +398,11 @@ impl ModuleLoader for EmbeddedModuleLoader { ); } - match self.shared.modules.read(original_specifier) { + match self + .shared + .modules + .read(original_specifier, VfsFileSubDataKind::ModuleGraph) + { Ok(Some(module)) => { let media_type = module.media_type; let (module_specifier, module_type, module_source) = @@ -495,6 +501,46 @@ impl ModuleLoader for EmbeddedModuleLoader { } std::future::ready(()).boxed_local() } + + fn get_source_map(&self, file_name: &str) -> Option> { + if file_name.starts_with("file:///") { + let url = + deno_path_util::url_from_directory_path(self.shared.vfs.root()).ok()?; + let file_url = ModuleSpecifier::parse(file_name).ok()?; + let relative_path = url.make_relative(&file_url)?; + self.shared.source_maps.get(&relative_path) + } else { + self.shared.source_maps.get(file_name) + } + // todo(https://github.com/denoland/deno_core/pull/1007): don't clone + .map(|s| s.as_bytes().to_vec()) + } + + fn get_source_mapped_source_line( + &self, + file_name: &str, + line_number: usize, + ) -> Option { + let specifier = ModuleSpecifier::parse(file_name).ok()?; + let data = self + .shared + .modules + .read(&specifier, VfsFileSubDataKind::Raw) + .ok()??; + + let source = String::from_utf8_lossy(&data.data); + // Do NOT use .lines(): it skips the terminating empty line. + // (due to internally using_terminator() instead of .split()) + let lines: Vec<&str> = source.split('\n').collect(); + if line_number >= lines.len() { + Some(format!( + "{} Couldn't format source line: Line {} is out of bounds (source may have changed at runtime)", + crate::colors::yellow("Warning"), line_number + 1, + )) + } else { + Some(lines[line_number].to_string()) + } + } } impl NodeRequireLoader for EmbeddedModuleLoader { @@ -590,6 +636,7 @@ pub async fn run(data: StandaloneData) -> Result { modules, npm_snapshot, root_path, + source_maps, vfs, } = data; let deno_dir_provider = Arc::new(DenoDirProvider::new(None)); @@ -841,6 +888,7 @@ pub async fn run(data: StandaloneData) -> Result { )), npm_resolver: npm_resolver.clone(), npm_req_resolver, + source_maps, vfs, workspace_resolver, }), diff --git a/cli/standalone/serialization.rs b/cli/standalone/serialization.rs index ac76172e37..8fe593c005 100644 --- a/cli/standalone/serialization.rs +++ b/cli/standalone/serialization.rs @@ -6,6 +6,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::io::Write; +use deno_ast::swc::common::source_map; use deno_ast::MediaType; use deno_core::anyhow::bail; use deno_core::anyhow::Context; @@ -20,12 +21,14 @@ use deno_npm::resolution::SerializedNpmResolutionSnapshotPackage; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::NpmPackageId; use deno_semver::package::PackageReq; +use indexmap::IndexMap; use crate::standalone::virtual_fs::VirtualDirectory; use super::binary::Metadata; use super::virtual_fs::BuiltVfs; use super::virtual_fs::VfsBuilder; +use super::virtual_fs::VirtualDirectoryEntries; const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd"; @@ -33,21 +36,22 @@ const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd"; /// * d3n0l4nd /// * /// * -/// * +/// * /// * /// * +/// * /// * d3n0l4nd pub fn serialize_binary_data_section( metadata: &Metadata, npm_snapshot: Option, remote_modules: &RemoteModulesStoreBuilder, + source_map_store: &SourceMapStore, vfs: &BuiltVfs, ) -> Result, AnyError> { let metadata = serde_json::to_string(metadata)?; let npm_snapshot = npm_snapshot.map(serialize_npm_snapshot).unwrap_or_default(); - let remote_modules_len = Cell::new(0_u64); - let serialized_vfs = serde_json::to_string(&vfs.root)?; + let serialized_vfs = serde_json::to_string(&vfs.entries)?; let bytes = capacity_builder::BytesBuilder::build(|builder| { builder.append(MAGIC_BYTES); @@ -63,10 +67,7 @@ pub fn serialize_binary_data_section( } // 3. Remote modules { - builder.append_le(remote_modules_len.get()); // this will be properly initialized on the second pass - let start_index = builder.len(); remote_modules.write(builder); - remote_modules_len.set((builder.len() - start_index) as u64); } // 4. VFS { @@ -78,6 +79,16 @@ pub fn serialize_binary_data_section( builder.append(file); } } + // 5. Source maps + { + builder.append_le(source_map_store.data.len() as u32); + for (specifier, source_map) in &source_map_store.data { + builder.append_le(specifier.len() as u32); + builder.append(specifier); + builder.append_le(source_map.len() as u32); + builder.append(source_map); + } + } // write the magic bytes at the end so we can use it // to make sure we've deserialized correctly @@ -91,19 +102,14 @@ pub struct DeserializedDataSection { pub metadata: Metadata, pub npm_snapshot: Option, pub remote_modules: RemoteModulesStore, - pub vfs_dir: VirtualDirectory, + pub source_maps: SourceMapStore, + pub vfs_root_entries: VirtualDirectoryEntries, pub vfs_files_data: &'static [u8], } pub fn deserialize_binary_data_section( data: &'static [u8], ) -> Result, AnyError> { - fn read_bytes_with_len(input: &[u8]) -> Result<(&[u8], &[u8]), AnyError> { - let (input, len) = read_u64(input)?; - let (input, data) = read_bytes(input, len as usize)?; - Ok((input, data)) - } - fn read_magic_bytes(input: &[u8]) -> Result<(&[u8], bool), AnyError> { if input.len() < MAGIC_BYTES.len() { bail!("Unexpected end of data. Could not find magic bytes."); @@ -115,34 +121,51 @@ pub fn deserialize_binary_data_section( Ok((input, true)) } + #[allow(clippy::type_complexity)] + fn read_source_map_entry( + input: &[u8], + ) -> Result<(&[u8], (Cow, Cow)), AnyError> { + let (input, specifier) = read_string_lossy(input)?; + let (input, source_map) = read_string_lossy(input)?; + Ok((input, (specifier, source_map))) + } + let (input, found) = read_magic_bytes(data)?; if !found { return Ok(None); } // 1. Metadata - let (input, data) = read_bytes_with_len(input).context("reading metadata")?; + let (input, data) = + read_bytes_with_u64_len(input).context("reading metadata")?; let metadata: Metadata = serde_json::from_slice(data).context("deserializing metadata")?; // 2. Npm snapshot let (input, data) = - read_bytes_with_len(input).context("reading npm snapshot")?; + read_bytes_with_u64_len(input).context("reading npm snapshot")?; let npm_snapshot = if data.is_empty() { None } else { Some(deserialize_npm_snapshot(data).context("deserializing npm snapshot")?) }; // 3. Remote modules - let (input, data) = - read_bytes_with_len(input).context("reading remote modules data")?; - let remote_modules = - RemoteModulesStore::build(data).context("deserializing remote modules")?; + let (input, remote_modules) = + RemoteModulesStore::build(input).context("deserializing remote modules")?; // 4. VFS - let (input, data) = read_bytes_with_len(input).context("vfs")?; - let vfs_dir: VirtualDirectory = + let (input, data) = read_bytes_with_u64_len(input).context("vfs")?; + let vfs_root_entries: VirtualDirectoryEntries = serde_json::from_slice(data).context("deserializing vfs data")?; let (input, vfs_files_data) = - read_bytes_with_len(input).context("reading vfs files data")?; + read_bytes_with_u64_len(input).context("reading vfs files data")?; + // 5. Source maps + let (mut input, source_map_data_len) = read_u32_as_usize(input)?; + let mut source_maps = SourceMapStore::with_capacity(source_map_data_len); + for _ in 0..source_map_data_len { + let (current_input, (specifier, source_map)) = + read_source_map_entry(input)?; + input = current_input; + source_maps.add(specifier, source_map); + } // finally ensure we read the magic bytes at the end let (_input, found) = read_magic_bytes(input)?; @@ -154,7 +177,8 @@ pub fn deserialize_binary_data_section( metadata, npm_snapshot, remote_modules, - vfs_dir, + source_maps, + vfs_root_entries, vfs_files_data, })) } @@ -162,19 +186,31 @@ pub fn deserialize_binary_data_section( #[derive(Default)] pub struct RemoteModulesStoreBuilder { specifiers: Vec<(String, u64)>, - data: Vec<(MediaType, Vec)>, + data: Vec<(MediaType, Vec, Option>)>, data_byte_len: u64, redirects: Vec<(String, String)>, redirects_len: u64, } impl RemoteModulesStoreBuilder { - pub fn add(&mut self, specifier: &Url, media_type: MediaType, data: Vec) { + pub fn add( + &mut self, + specifier: &Url, + media_type: MediaType, + data: Vec, + maybe_transpiled: Option>, + ) { log::debug!("Adding '{}' ({})", specifier, media_type); let specifier = specifier.to_string(); self.specifiers.push((specifier, self.data_byte_len)); - self.data_byte_len += 1 + 8 + data.len() as u64; // media type (1 byte), data length (8 bytes), data - self.data.push((media_type, data)); + let maybe_transpiled_len = match &maybe_transpiled { + // data length (4 bytes), data + Some(data) => 4 + data.len() as u64, + None => 0, + }; + // media type (1 byte), data length (4 bytes), data, has transpiled (1 byte), transpiled length + self.data_byte_len += 1 + 4 + data.len() as u64 + 1 + maybe_transpiled_len; + self.data.push((media_type, data, maybe_transpiled)); } pub fn add_redirects(&mut self, redirects: &BTreeMap) { @@ -193,7 +229,7 @@ impl RemoteModulesStoreBuilder { builder.append_le(self.redirects.len() as u32); for (specifier, offset) in &self.specifiers { builder.append_le(specifier.len() as u32); - builder.append(specifier.as_bytes()); + builder.append(specifier); builder.append_le(*offset); } for (from, to) in &self.redirects { @@ -202,10 +238,32 @@ impl RemoteModulesStoreBuilder { builder.append_le(to.len() as u32); builder.append(to); } - for (media_type, data) in &self.data { + builder.append_le( + self + .data + .iter() + .map(|(_, data, maybe_transpiled)| { + 1 + 4 + + (data.len() as u64) + + 1 + + match maybe_transpiled { + Some(transpiled) => 4 + (transpiled.len() as u64), + None => 0, + } + }) + .sum::(), + ); + for (media_type, data, maybe_transpiled) in &self.data { builder.append(serialize_media_type(*media_type)); - builder.append_le(data.len() as u64); + builder.append_le(data.len() as u32); builder.append(data); + if let Some(transpiled) = maybe_transpiled { + builder.append(1); + builder.append_le(transpiled.len() as u32); + builder.append(transpiled); + } else { + builder.append(0); + } } } } @@ -234,6 +292,30 @@ impl DenoCompileModuleSource { } } +pub struct SourceMapStore { + data: IndexMap, Cow<'static, str>>, +} + +impl SourceMapStore { + pub fn with_capacity(capacity: usize) -> Self { + Self { + data: IndexMap::with_capacity(capacity), + } + } + + pub fn add( + &mut self, + specifier: Cow<'static, str>, + source_map: Cow<'static, str>, + ) { + self.data.insert(specifier, source_map); + } + + pub fn get(&self, specifier: &str) -> Option<&Cow<'static, str>> { + self.data.get(specifier) + } +} + pub struct DenoCompileModuleData<'a> { pub specifier: &'a Url, pub media_type: MediaType, @@ -280,6 +362,13 @@ impl<'a> DenoCompileModuleData<'a> { } } +pub struct RemoteModuleEntry<'a> { + pub specifier: &'a Url, + pub media_type: MediaType, + pub data: Cow<'static, [u8]>, + pub transpiled_data: Option>, +} + enum RemoteModulesStoreSpecifierValue { Data(usize), Redirect(Url), @@ -291,7 +380,7 @@ pub struct RemoteModulesStore { } impl RemoteModulesStore { - fn build(data: &'static [u8]) -> Result { + fn build(input: &'static [u8]) -> Result<(&'static [u8], Self), AnyError> { fn read_specifier(input: &[u8]) -> Result<(&[u8], (Url, u64)), AnyError> { let (input, specifier) = read_string_lossy(input)?; let specifier = Url::parse(&specifier)?; @@ -334,12 +423,16 @@ impl RemoteModulesStore { Ok((input, specifiers)) } - let (files_data, specifiers) = read_headers(data)?; + let (input, specifiers) = read_headers(input)?; + let (input, files_data) = read_bytes_with_u64_len(input)?; - Ok(Self { - specifiers, - files_data, - }) + Ok(( + input, + Self { + specifiers, + files_data, + }, + )) } pub fn resolve_specifier<'a>( @@ -370,7 +463,7 @@ impl RemoteModulesStore { pub fn read<'a>( &'a self, original_specifier: &'a Url, - ) -> Result>, AnyError> { + ) -> Result>, AnyError> { let mut count = 0; let mut specifier = original_specifier; loop { @@ -386,12 +479,25 @@ impl RemoteModulesStore { let input = &self.files_data[*offset..]; let (input, media_type_byte) = read_bytes(input, 1)?; let media_type = deserialize_media_type(media_type_byte[0])?; - let (input, len) = read_u64(input)?; - let (_input, data) = read_bytes(input, len as usize)?; - return Ok(Some(DenoCompileModuleData { + let (input, data) = read_bytes_with_u32_len(input)?; + check_has_len(input, 1)?; + let (input, has_transpiled) = (&input[1..], input[0]); + let (_, transpiled_data) = match has_transpiled { + 0 => (input, None), + 1 => { + let (input, data) = read_bytes_with_u32_len(input)?; + (input, Some(data)) + } + value => bail!( + "Invalid transpiled data flag: {}. Compiled data is corrupt.", + value + ), + }; + return Ok(Some(RemoteModuleEntry { specifier, media_type, data: Cow::Borrowed(data), + transpiled_data: transpiled_data.map(Cow::Borrowed), })); } None => { @@ -630,14 +736,32 @@ fn parse_vec_n_times_with_index( Ok((input, results)) } +fn read_bytes_with_u64_len(input: &[u8]) -> Result<(&[u8], &[u8]), AnyError> { + let (input, len) = read_u64(input)?; + let (input, data) = read_bytes(input, len as usize)?; + Ok((input, data)) +} + +fn read_bytes_with_u32_len(input: &[u8]) -> Result<(&[u8], &[u8]), AnyError> { + let (input, len) = read_u32_as_usize(input)?; + let (input, data) = read_bytes(input, len)?; + Ok((input, data)) +} + fn read_bytes(input: &[u8], len: usize) -> Result<(&[u8], &[u8]), AnyError> { - if input.len() < len { - bail!("Unexpected end of data.",); - } + check_has_len(input, len)?; let (len_bytes, input) = input.split_at(len); Ok((input, len_bytes)) } +#[inline(always)] +fn check_has_len(input: &[u8], len: usize) -> Result<(), AnyError> { + if input.len() < len { + bail!("Unexpected end of data."); + } + Ok(()) +} + fn read_string_lossy(input: &[u8]) -> Result<(&[u8], Cow), AnyError> { let (input, str_len) = read_u32_as_usize(input)?; let (input, data_bytes) = read_bytes(input, str_len)?; diff --git a/cli/standalone/virtual_fs.rs b/cli/standalone/virtual_fs.rs index 04e66d680e..522fe47dd9 100644 --- a/cli/standalone/virtual_fs.rs +++ b/cli/standalone/virtual_fs.rs @@ -67,7 +67,7 @@ impl WindowsSystemRootablePath { #[derive(Debug)] pub struct BuiltVfs { pub root_path: WindowsSystemRootablePath, - pub root: VirtualDirectory, + pub entries: VirtualDirectoryEntries, pub files: Vec>, } @@ -95,7 +95,7 @@ impl VfsBuilder { Self { executable_root: VirtualDirectory { name: "/".to_string(), - entries: Vec::new(), + entries: Default::default(), }, files: Vec::new(), current_offset: 0, @@ -208,23 +208,20 @@ impl VfsBuilder { continue; } let name = component.as_os_str().to_string_lossy(); - let index = match current_dir - .entries - .binary_search_by(|e| e.name().cmp(&name)) - { + let index = match current_dir.entries.binary_search(&name) { Ok(index) => index, Err(insert_index) => { - current_dir.entries.insert( + current_dir.entries.0.insert( insert_index, VfsEntry::Dir(VirtualDirectory { name: name.to_string(), - entries: Vec::new(), + entries: Default::default(), }), ); insert_index } }; - match &mut current_dir.entries[index] { + match &mut current_dir.entries.0[index] { VfsEntry::Dir(dir) => { current_dir = dir; } @@ -248,14 +245,8 @@ impl VfsBuilder { continue; } let name = component.as_os_str().to_string_lossy(); - let index = match current_dir - .entries - .binary_search_by(|e| e.name().cmp(&name)) - { - Ok(index) => index, - Err(_) => return None, - }; - match &mut current_dir.entries[index] { + let entry = current_dir.entries.get_mut_by_name(&name)?; + match entry { VfsEntry::Dir(dir) => { current_dir = dir; } @@ -320,9 +311,9 @@ impl VfsBuilder { offset, len: data.len() as u64, }; - match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { + match dir.entries.binary_search(&name) { Ok(index) => { - let entry = &mut dir.entries[index]; + let entry = &mut dir.entries.0[index]; match entry { VfsEntry::File(virtual_file) => match sub_data_kind { VfsFileSubDataKind::Raw => { @@ -336,7 +327,7 @@ impl VfsBuilder { } } Err(insert_index) => { - dir.entries.insert( + dir.entries.0.insert( insert_index, VfsEntry::File(VirtualFile { name: name.to_string(), @@ -384,10 +375,10 @@ impl VfsBuilder { let target = normalize_path(path.parent().unwrap().join(&target)); let dir = self.add_dir_raw(path.parent().unwrap()); let name = path.file_name().unwrap().to_string_lossy(); - match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { + match dir.entries.binary_search(&name) { Ok(_) => {} // previously inserted Err(insert_index) => { - dir.entries.insert( + dir.entries.0.insert( insert_index, VfsEntry::Symlink(VirtualSymlink { name: name.to_string(), @@ -426,7 +417,7 @@ impl VfsBuilder { dir: &mut VirtualDirectory, parts: &[String], ) { - for entry in &mut dir.entries { + for entry in &mut dir.entries.0 { match entry { VfsEntry::Dir(dir) => { strip_prefix_from_symlinks(dir, parts); @@ -454,13 +445,13 @@ impl VfsBuilder { if self.min_root_dir.as_ref() == Some(¤t_path) { break; } - match ¤t_dir.entries[0] { + match ¤t_dir.entries.0[0] { VfsEntry::Dir(dir) => { if dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME { // special directory we want to maintain break; } - match current_dir.entries.remove(0) { + match current_dir.entries.0.remove(0) { VfsEntry::Dir(dir) => { current_path = WindowsSystemRootablePath::Path(current_path.join(&dir.name)); @@ -480,7 +471,7 @@ impl VfsBuilder { } BuiltVfs { root_path: current_path, - root: current_dir, + entries: current_dir.entries, files: self.files, } } @@ -506,7 +497,7 @@ pub fn output_vfs(vfs: &BuiltVfs, executable_name: &str) { return; // no need to compute if won't output } - if vfs.root.entries.is_empty() { + if vfs.entries.is_empty() { return; // nothing to output } @@ -696,7 +687,7 @@ fn vfs_as_display_tree( fn dir_size(dir: &VirtualDirectory, seen_offsets: &mut HashSet) -> Size { let mut size = Size::default(); - for entry in &dir.entries { + for entry in dir.entries.iter() { match entry { VfsEntry::Dir(virtual_directory) => { size = size + dir_size(virtual_directory, seen_offsets); @@ -760,15 +751,10 @@ fn vfs_as_display_tree( fn include_all_entries<'a>( dir_path: &WindowsSystemRootablePath, - vfs_dir: &'a VirtualDirectory, + entries: &'a VirtualDirectoryEntries, seen_offsets: &mut HashSet, ) -> Vec> { - if vfs_dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME { - return show_global_node_modules_dir(vfs_dir, seen_offsets); - } - - vfs_dir - .entries + entries .iter() .map(|entry| DirEntryOutput { name: Cow::Borrowed(entry.name()), @@ -826,10 +812,12 @@ fn vfs_as_display_tree( } else { EntryOutput::Subset(children) } + } else if vfs_dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME { + EntryOutput::Subset(show_global_node_modules_dir(vfs_dir, seen_offsets)) } else { EntryOutput::Subset(include_all_entries( &WindowsSystemRootablePath::Path(dir), - vfs_dir, + &vfs_dir.entries, seen_offsets, )) } @@ -839,7 +827,7 @@ fn vfs_as_display_tree( // user might not have context about what's being shown let mut seen_offsets = HashSet::with_capacity(vfs.files.len()); let mut child_entries = - include_all_entries(&vfs.root_path, &vfs.root, &mut seen_offsets); + include_all_entries(&vfs.root_path, &vfs.entries, &mut seen_offsets); for child_entry in &mut child_entries { child_entry.collapse_leaf_nodes(); } @@ -961,27 +949,70 @@ impl VfsEntry { } } +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct VirtualDirectoryEntries(Vec); + +impl VirtualDirectoryEntries { + pub fn new(mut entries: Vec) -> Self { + // needs to be sorted by name + entries.sort_by(|a, b| a.name().cmp(b.name())); + Self(entries) + } + + pub fn take_inner(&mut self) -> Vec { + std::mem::take(&mut self.0) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get_by_name(&self, name: &str) -> Option<&VfsEntry> { + self.binary_search(name).ok().map(|index| &self.0[index]) + } + + pub fn get_mut_by_name(&mut self, name: &str) -> Option<&mut VfsEntry> { + self + .binary_search(name) + .ok() + .map(|index| &mut self.0[index]) + } + + pub fn binary_search(&self, name: &str) -> Result { + self.0.binary_search_by(|e| e.name().cmp(name)) + } + + pub fn insert(&mut self, entry: VfsEntry) { + match self.binary_search(entry.name()) { + Ok(index) => { + self.0[index] = entry; + } + Err(insert_index) => { + self.0.insert(insert_index, entry); + } + } + } + + pub fn remove(&mut self, index: usize) -> VfsEntry { + self.0.remove(index) + } + + pub fn iter(&self) -> std::slice::Iter<'_, VfsEntry> { + self.0.iter() + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct VirtualDirectory { #[serde(rename = "n")] pub name: String, // should be sorted by name #[serde(rename = "e")] - pub entries: Vec, -} - -impl VirtualDirectory { - pub fn insert_entry(&mut self, entry: VfsEntry) { - let name = entry.name(); - match self.entries.binary_search_by(|e| e.name().cmp(name)) { - Ok(index) => { - self.entries[index] = entry; - } - Err(insert_index) => { - self.entries.insert(insert_index, entry); - } - } - } + pub entries: VirtualDirectoryEntries, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -1136,20 +1167,13 @@ impl VfsRoot { } }; let component = component.to_string_lossy(); - match current_dir + current_entry = current_dir .entries - .binary_search_by(|e| e.name().cmp(&component)) - { - Ok(index) => { - current_entry = current_dir.entries[index].as_ref(); - } - Err(_) => { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "path not found", - )); - } - } + .get_by_name(&component) + .ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "path not found") + })? + .as_ref(); } Ok((final_path, current_entry)) @@ -1706,7 +1730,10 @@ mod test { FileBackedVfs::new( Cow::Owned(data), VfsRoot { - dir: vfs.root, + dir: VirtualDirectory { + name: "".to_string(), + entries: vfs.entries, + }, root_path: dest_path.to_path_buf(), start_file_offset: 0, }, diff --git a/tests/integration/compile_tests.rs b/tests/integration/compile_tests.rs index a69e873ab4..a715233933 100644 --- a/tests/integration/compile_tests.rs +++ b/tests/integration/compile_tests.rs @@ -2,7 +2,6 @@ use deno_core::serde_json; use test_util as util; -use util::assert_contains; use util::assert_not_contains; use util::testdata_path; use util::TestContext; @@ -90,78 +89,6 @@ fn standalone_args() { .assert_exit_code(0); } -#[test] -fn standalone_error() { - let context = TestContextBuilder::new().build(); - let dir = context.temp_dir(); - let exe = if cfg!(windows) { - dir.path().join("error.exe") - } else { - dir.path().join("error") - }; - context - .new_command() - .args_vec([ - "compile", - "--output", - &exe.to_string_lossy(), - "./compile/standalone_error.ts", - ]) - .run() - .skip_output_check() - .assert_exit_code(0); - - let output = context.new_command().name(&exe).split_output().run(); - output.assert_exit_code(1); - output.assert_stdout_matches_text(""); - let stderr = output.stderr(); - // On Windows, we cannot assert the file path (because '\'). - // Instead we just check for relevant output. - assert_contains!(stderr, "error: Uncaught (in promise) Error: boom!"); - assert_contains!(stderr, "\n at boom (file://"); - assert_contains!(stderr, "standalone_error.ts:2:9"); - assert_contains!(stderr, "at foo (file://"); - assert_contains!(stderr, "standalone_error.ts:5:3"); - assert_contains!(stderr, "standalone_error.ts:7:1"); -} - -#[test] -fn standalone_error_module_with_imports() { - let context = TestContextBuilder::new().build(); - let dir = context.temp_dir(); - let exe = if cfg!(windows) { - dir.path().join("error.exe") - } else { - dir.path().join("error") - }; - context - .new_command() - .args_vec([ - "compile", - "--output", - &exe.to_string_lossy(), - "./compile/standalone_error_module_with_imports_1.ts", - ]) - .run() - .skip_output_check() - .assert_exit_code(0); - - let output = context - .new_command() - .name(&exe) - .env("NO_COLOR", "1") - .split_output() - .run(); - output.assert_stdout_matches_text("hello\n"); - let stderr = output.stderr(); - // On Windows, we cannot assert the file path (because '\'). - // Instead we just check for relevant output. - assert_contains!(stderr, "error: Uncaught (in promise) Error: boom!"); - assert_contains!(stderr, "\n at file://"); - assert_contains!(stderr, "standalone_error_module_with_imports_2.ts:2:7"); - output.assert_exit_code(1); -} - #[test] fn standalone_load_datauri() { let context = TestContextBuilder::new().build(); diff --git a/tests/specs/compile/code_cache/__test__.jsonc b/tests/specs/compile/code_cache/__test__.jsonc index 72353e27da..f1c3461adc 100644 --- a/tests/specs/compile/code_cache/__test__.jsonc +++ b/tests/specs/compile/code_cache/__test__.jsonc @@ -1,6 +1,9 @@ { "tempDir": true, "steps": [{ + "args": "run -A cleanup.ts", + "output": "[WILDCARD]" + }, { "if": "unix", "args": "compile --output using_code_cache --log-level=debug main.ts", "output": "[WILDCARD]" diff --git a/tests/specs/compile/code_cache/cleanup.ts b/tests/specs/compile/code_cache/cleanup.ts new file mode 100644 index 0000000000..d9e7c805f8 --- /dev/null +++ b/tests/specs/compile/code_cache/cleanup.ts @@ -0,0 +1,11 @@ +import { tmpdir } from "node:os"; + +// cleanup the code cache file from a previous run +try { + if (Deno.build.os === "windows") { + Deno.removeSync(tmpdir() + "\\deno-compile-using_code_cache.exe.cache"); + } else { + Deno.removeSync(tmpdir() + "\\deno-compile-using_code_cache.cache"); + } +} catch { +} diff --git a/tests/specs/compile/determinism/__test__.jsonc b/tests/specs/compile/determinism/__test__.jsonc index 97045744f1..b84a1fdf18 100644 --- a/tests/specs/compile/determinism/__test__.jsonc +++ b/tests/specs/compile/determinism/__test__.jsonc @@ -1,28 +1,31 @@ { "tempDir": true, "steps": [{ - "if": "unix", - "args": "compile --output main1 main.ts", + "args": "run -A setup.ts", "output": "[WILDCARD]" }, { "if": "unix", - "args": "compile --output main2 main.ts", + "args": "compile --no-config --output a/main a/main.ts", "output": "[WILDCARD]" }, { "if": "unix", - "args": "run --allow-read=. assert_equal.ts main1 main2", + "args": "compile --no-config --output b/main b/main.ts", + "output": "[WILDCARD]" + }, { + "if": "unix", + "args": "run --allow-read=. assert_equal.ts a/main b/main", "output": "Same\n" }, { "if": "windows", - "args": "compile --output main1.exe main.ts", + "args": "compile --no-config --output a/main.exe a/main.ts", "output": "[WILDCARD]" }, { "if": "windows", - "args": "compile --output main2.exe main.ts", + "args": "compile --no-config --output b/main.exe b/main.ts", "output": "[WILDCARD]" }, { "if": "windows", - "args": "run --allow-read=. assert_equal.ts main1.exe main2.exe", + "args": "run --allow-read=. assert_equal.ts a/main.exe b/main.exe", "output": "Same\n" }] } diff --git a/tests/specs/compile/determinism/setup.ts b/tests/specs/compile/determinism/setup.ts new file mode 100644 index 0000000000..8bb5753079 --- /dev/null +++ b/tests/specs/compile/determinism/setup.ts @@ -0,0 +1,10 @@ +// for setup, we create two directories with the same file in each +// and then when compiling we ensure this directory name has no +// effect on the output +makeCopyDir("a"); +makeCopyDir("b"); + +function makeCopyDir(dirName) { + Deno.mkdirSync(dirName); + Deno.copyFileSync("main.ts", `${dirName}/main.ts`); +} diff --git a/tests/specs/compile/error/local/__test__.jsonc b/tests/specs/compile/error/local/__test__.jsonc new file mode 100644 index 0000000000..8d6a015a51 --- /dev/null +++ b/tests/specs/compile/error/local/__test__.jsonc @@ -0,0 +1,24 @@ +{ + "tempDir": true, + "steps": [{ + "if": "unix", + "args": "compile --output main standalone_error.ts", + "output": "[WILDCARD]" + }, { + "if": "unix", + "commandName": "./main", + "args": [], + "output": "output.out", + "exitCode": 1 + }, { + "if": "windows", + "args": "compile --output main.exe standalone_error.ts", + "output": "[WILDCARD]" + }, { + "if": "windows", + "commandName": "./main.exe", + "args": [], + "output": "output.out", + "exitCode": 1 + }] +} diff --git a/tests/specs/compile/error/local/output.out b/tests/specs/compile/error/local/output.out new file mode 100644 index 0000000000..b346734ae6 --- /dev/null +++ b/tests/specs/compile/error/local/output.out @@ -0,0 +1,6 @@ +error: Uncaught (in promise) Error: boom! + throw new Error("boom!"); + ^ + at boom (file:///[WILDLINE]standalone_error.ts:2:9) + at foo (file:///[WILDLINE]standalone_error.ts:6:3) + at file:///[WILDLINE]standalone_error.ts:9:1 diff --git a/tests/testdata/compile/standalone_error.ts b/tests/specs/compile/error/local/standalone_error.ts similarity index 100% rename from tests/testdata/compile/standalone_error.ts rename to tests/specs/compile/error/local/standalone_error.ts diff --git a/tests/specs/compile/error/remote/__test__.jsonc b/tests/specs/compile/error/remote/__test__.jsonc new file mode 100644 index 0000000000..9ad9091ec6 --- /dev/null +++ b/tests/specs/compile/error/remote/__test__.jsonc @@ -0,0 +1,24 @@ +{ + "tempDir": true, + "steps": [{ + "if": "unix", + "args": "compile -A --output main main.ts", + "output": "[WILDCARD]" + }, { + "if": "unix", + "commandName": "./main", + "args": [], + "output": "output.out", + "exitCode": 1 + }, { + "if": "windows", + "args": "compile -A --output main.exe main.ts", + "output": "[WILDCARD]" + }, { + "if": "windows", + "commandName": "./main.exe", + "args": [], + "output": "output.out", + "exitCode": 1 + }] +} diff --git a/tests/specs/compile/error/remote/main.ts b/tests/specs/compile/error/remote/main.ts new file mode 100644 index 0000000000..7a27276dd8 --- /dev/null +++ b/tests/specs/compile/error/remote/main.ts @@ -0,0 +1 @@ +import "http://localhost:4545/compile/standalone_error_module_with_imports_1.ts"; diff --git a/tests/specs/compile/error/remote/output.out b/tests/specs/compile/error/remote/output.out new file mode 100644 index 0000000000..3e23694c16 --- /dev/null +++ b/tests/specs/compile/error/remote/output.out @@ -0,0 +1,5 @@ +hello +error: Uncaught (in promise) Error: boom! +throw new Error(value); + ^ + at http://localhost:4545/compile/standalone_error_module_with_imports_2.ts:7:7 diff --git a/tests/testdata/compile/standalone_error_module_with_imports_2.ts b/tests/testdata/compile/standalone_error_module_with_imports_2.ts index ef052b512e..c83d7ceea6 100644 --- a/tests/testdata/compile/standalone_error_module_with_imports_2.ts +++ b/tests/testdata/compile/standalone_error_module_with_imports_2.ts @@ -1,2 +1,7 @@ +// file has blank lines to make the input line +// different than the output console.log("hello"); -throw new Error("boom!"); + +const value: string = "boom!"; + +throw new Error(value);