diff options
Diffstat (limited to 'cli/tools')
-rw-r--r-- | cli/tools/registry/diagnostics.rs | 32 | ||||
-rw-r--r-- | cli/tools/registry/mod.rs | 23 | ||||
-rw-r--r-- | cli/tools/registry/tar.rs | 8 | ||||
-rw-r--r-- | cli/tools/registry/unfurl.rs | 496 |
4 files changed, 537 insertions, 22 deletions
diff --git a/cli/tools/registry/diagnostics.rs b/cli/tools/registry/diagnostics.rs index b605c293b..78cc6f555 100644 --- a/cli/tools/registry/diagnostics.rs +++ b/cli/tools/registry/diagnostics.rs @@ -20,7 +20,7 @@ use deno_core::error::AnyError; use deno_graph::FastCheckDiagnostic; use lsp_types::Url; -use crate::util::import_map::ImportMapUnfurlDiagnostic; +use super::unfurl::SpecifierUnfurlerDiagnostic; #[derive(Clone, Default)] pub struct PublishDiagnosticsCollector { @@ -74,7 +74,7 @@ impl PublishDiagnosticsCollector { pub enum PublishDiagnostic { FastCheck(FastCheckDiagnostic), - ImportMapUnfurl(ImportMapUnfurlDiagnostic), + SpecifierUnfurl(SpecifierUnfurlerDiagnostic), InvalidPath { path: PathBuf, message: String, @@ -102,7 +102,7 @@ impl Diagnostic for PublishDiagnostic { .. }) => DiagnosticLevel::Warning, FastCheck(_) => DiagnosticLevel::Error, - ImportMapUnfurl(_) => DiagnosticLevel::Warning, + SpecifierUnfurl(_) => DiagnosticLevel::Warning, InvalidPath { .. } => DiagnosticLevel::Error, DuplicatePath { .. } => DiagnosticLevel::Error, UnsupportedFileType { .. } => DiagnosticLevel::Warning, @@ -114,7 +114,7 @@ impl Diagnostic for PublishDiagnostic { use PublishDiagnostic::*; match &self { FastCheck(diagnostic) => diagnostic.code(), - ImportMapUnfurl(diagnostic) => Cow::Borrowed(diagnostic.code()), + SpecifierUnfurl(diagnostic) => Cow::Borrowed(diagnostic.code()), InvalidPath { .. } => Cow::Borrowed("invalid-path"), DuplicatePath { .. } => Cow::Borrowed("case-insensitive-duplicate-path"), UnsupportedFileType { .. } => Cow::Borrowed("unsupported-file-type"), @@ -126,7 +126,7 @@ impl Diagnostic for PublishDiagnostic { use PublishDiagnostic::*; match &self { FastCheck(diagnostic) => diagnostic.message(), - ImportMapUnfurl(diagnostic) => Cow::Borrowed(diagnostic.message()), + SpecifierUnfurl(diagnostic) => Cow::Borrowed(diagnostic.message()), InvalidPath { message, .. } => Cow::Borrowed(message.as_str()), DuplicatePath { .. } => { Cow::Borrowed("package path is a case insensitive duplicate of another path in the package") @@ -142,8 +142,8 @@ impl Diagnostic for PublishDiagnostic { use PublishDiagnostic::*; match &self { FastCheck(diagnostic) => diagnostic.location(), - ImportMapUnfurl(diagnostic) => match diagnostic { - ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { + SpecifierUnfurl(diagnostic) => match diagnostic { + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { specifier, text_info, range, @@ -180,8 +180,8 @@ impl Diagnostic for PublishDiagnostic { fn snippet(&self) -> Option<DiagnosticSnippet<'_>> { match &self { PublishDiagnostic::FastCheck(diagnostic) => diagnostic.snippet(), - PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { - ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { + PublishDiagnostic::SpecifierUnfurl(diagnostic) => match diagnostic { + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { text_info, range, .. @@ -227,7 +227,7 @@ impl Diagnostic for PublishDiagnostic { fn hint(&self) -> Option<Cow<'_, str>> { match &self { PublishDiagnostic::FastCheck(diagnostic) => diagnostic.hint(), - PublishDiagnostic::ImportMapUnfurl(_) => None, + PublishDiagnostic::SpecifierUnfurl(_) => None, PublishDiagnostic::InvalidPath { .. } => Some( Cow::Borrowed("rename or remove the file, or add it to 'publish.exclude' in the config file"), ), @@ -250,11 +250,11 @@ impl Diagnostic for PublishDiagnostic { PublishDiagnostic::FastCheck(diagnostic) => { diagnostic.info() } - PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { - ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[ - Cow::Borrowed("after publishing this package, imports from the local import map do not work"), + PublishDiagnostic::SpecifierUnfurl(diagnostic) => match diagnostic { + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[ + Cow::Borrowed("after publishing this package, imports from the local import map / package.json do not work"), Cow::Borrowed("dynamic imports that can not be analyzed at publish time will not be rewritten automatically"), - Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map") + Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map / package.json") ]), }, PublishDiagnostic::InvalidPath { .. } => Cow::Borrowed(&[ @@ -278,8 +278,8 @@ impl Diagnostic for PublishDiagnostic { fn docs_url(&self) -> Option<Cow<'_, str>> { match &self { PublishDiagnostic::FastCheck(diagnostic) => diagnostic.docs_url(), - PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { - ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => None, + PublishDiagnostic::SpecifierUnfurl(diagnostic) => match diagnostic { + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => None, }, PublishDiagnostic::InvalidPath { .. } => { Some(Cow::Borrowed("https://jsr.io/go/invalid-path")) diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index 52d303ae1..eadd0e44d 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -35,13 +35,13 @@ use crate::factory::CliFactory; use crate::graph_util::ModuleGraphCreator; use crate::http_util::HttpClient; use crate::resolver::MappedSpecifierResolver; +use crate::resolver::SloppyImportsResolver; use crate::tools::check::CheckOptions; use crate::tools::lint::no_slow_types; use crate::tools::registry::diagnostics::PublishDiagnostic; use crate::tools::registry::diagnostics::PublishDiagnosticsCollector; use crate::tools::registry::graph::collect_invalid_external_imports; use crate::util::display::human_size; -use crate::util::import_map::ImportMapUnfurler; mod api; mod auth; @@ -50,10 +50,13 @@ mod graph; mod paths; mod publish_order; mod tar; +mod unfurl; use auth::get_auth_method; use auth::AuthMethod; use publish_order::PublishOrderGraph; +pub use unfurl::deno_json_deps; +use unfurl::SpecifierUnfurler; use super::check::TypeChecker; @@ -81,12 +84,15 @@ impl PreparedPublishPackage { static SUGGESTED_ENTRYPOINTS: [&str; 4] = ["mod.ts", "mod.js", "index.ts", "index.js"]; +#[allow(clippy::too_many_arguments)] async fn prepare_publish( package_name: &str, deno_json: &ConfigFile, source_cache: Arc<ParsedSourceCache>, graph: Arc<deno_graph::ModuleGraph>, mapped_resolver: Arc<MappedSpecifierResolver>, + sloppy_imports_resolver: Option<SloppyImportsResolver>, + bare_node_builtins: bool, diagnostics_collector: &PublishDiagnosticsCollector, ) -> Result<Rc<PreparedPublishPackage>, AnyError> { let config_path = deno_json.specifier.to_file_path().unwrap(); @@ -132,7 +138,11 @@ async fn prepare_publish( let diagnostics_collector = diagnostics_collector.clone(); let tarball = deno_core::unsync::spawn_blocking(move || { - let unfurler = ImportMapUnfurler::new(&mapped_resolver); + let unfurler = SpecifierUnfurler::new( + &mapped_resolver, + sloppy_imports_resolver.as_ref(), + bare_node_builtins, + ); tar::create_gzipped_tarball( &dir_path, LazyGraphSourceParser::new(&source_cache, &graph), @@ -661,7 +671,9 @@ async fn prepare_packages_for_publishing( let module_graph_creator = cli_factory.module_graph_creator().await?.as_ref(); let source_cache = cli_factory.parsed_source_cache(); let type_checker = cli_factory.type_checker().await?; + let fs = cli_factory.fs(); let cli_options = cli_factory.cli_options(); + let bare_node_builtins = cli_options.unstable_bare_node_builtins(); if members.len() > 1 { println!("Publishing a workspace..."); @@ -686,6 +698,11 @@ async fn prepare_packages_for_publishing( .into_iter() .map(|member| { let mapped_resolver = mapped_resolver.clone(); + let sloppy_imports_resolver = if cli_options.unstable_sloppy_imports() { + Some(SloppyImportsResolver::new(fs.clone())) + } else { + None + }; let graph = graph.clone(); async move { let package = prepare_publish( @@ -694,6 +711,8 @@ async fn prepare_packages_for_publishing( source_cache.clone(), graph, mapped_resolver, + sloppy_imports_resolver, + bare_node_builtins, diagnostics_collector, ) .await diff --git a/cli/tools/registry/tar.rs b/cli/tools/registry/tar.rs index 66d15b5a6..a8519fe02 100644 --- a/cli/tools/registry/tar.rs +++ b/cli/tools/registry/tar.rs @@ -18,10 +18,10 @@ use tar::Header; use crate::cache::LazyGraphSourceParser; use crate::tools::registry::paths::PackagePath; -use crate::util::import_map::ImportMapUnfurler; use super::diagnostics::PublishDiagnostic; use super::diagnostics::PublishDiagnosticsCollector; +use super::unfurl::SpecifierUnfurler; #[derive(Debug, Clone, PartialEq)] pub struct PublishableTarballFile { @@ -40,7 +40,7 @@ pub fn create_gzipped_tarball( dir: &Path, source_parser: LazyGraphSourceParser, diagnostics_collector: &PublishDiagnosticsCollector, - unfurler: &ImportMapUnfurler, + unfurler: &SpecifierUnfurler, file_patterns: Option<FilePatterns>, ) -> Result<PublishableTarball, AnyError> { let mut tar = TarGzArchive::new(); @@ -192,7 +192,7 @@ pub fn create_gzipped_tarball( fn resolve_content_maybe_unfurling( path: &Path, specifier: &Url, - unfurler: &ImportMapUnfurler, + unfurler: &SpecifierUnfurler, source_parser: LazyGraphSourceParser, diagnostics_collector: &PublishDiagnosticsCollector, ) -> Result<Vec<u8>, AnyError> { @@ -241,7 +241,7 @@ fn resolve_content_maybe_unfurling( log::debug!("Unfurling {}", specifier); let mut reporter = |diagnostic| { - diagnostics_collector.push(PublishDiagnostic::ImportMapUnfurl(diagnostic)); + diagnostics_collector.push(PublishDiagnostic::SpecifierUnfurl(diagnostic)); }; let content = unfurler.unfurl(specifier, &parsed_source, &mut reporter); Ok(content.into_bytes()) diff --git a/cli/tools/registry/unfurl.rs b/cli/tools/registry/unfurl.rs new file mode 100644 index 000000000..abea2bd0c --- /dev/null +++ b/cli/tools/registry/unfurl.rs @@ -0,0 +1,496 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::collections::HashSet; + +use deno_ast::ParsedSource; +use deno_ast::SourceRange; +use deno_ast::SourceTextInfo; +use deno_core::serde_json; +use deno_core::ModuleSpecifier; +use deno_graph::DefaultModuleAnalyzer; +use deno_graph::DependencyDescriptor; +use deno_graph::DynamicTemplatePart; +use deno_graph::TypeScriptReference; +use deno_runtime::deno_node::is_builtin_node_module; +use deno_semver::jsr::JsrDepPackageReq; +use deno_semver::jsr::JsrPackageReqReference; +use deno_semver::npm::NpmPackageReqReference; + +use crate::resolver::MappedSpecifierResolver; +use crate::resolver::SloppyImportsResolver; + +pub fn deno_json_deps( + config: &deno_config::ConfigFile, +) -> HashSet<JsrDepPackageReq> { + let values = imports_values(config.json.imports.as_ref()) + .into_iter() + .chain(scope_values(config.json.scopes.as_ref())); + values_to_set(values) +} + +fn imports_values(value: Option<&serde_json::Value>) -> Vec<&String> { + let Some(obj) = value.and_then(|v| v.as_object()) else { + return Vec::new(); + }; + let mut items = Vec::with_capacity(obj.len()); + for value in obj.values() { + if let serde_json::Value::String(value) = value { + items.push(value); + } + } + items +} + +fn scope_values(value: Option<&serde_json::Value>) -> Vec<&String> { + let Some(obj) = value.and_then(|v| v.as_object()) else { + return Vec::new(); + }; + obj.values().flat_map(|v| imports_values(Some(v))).collect() +} + +fn values_to_set<'a>( + values: impl Iterator<Item = &'a String>, +) -> HashSet<JsrDepPackageReq> { + let mut entries = HashSet::new(); + for value in values { + if let Ok(req_ref) = JsrPackageReqReference::from_str(value) { + entries.insert(JsrDepPackageReq::jsr(req_ref.into_inner().req)); + } else if let Ok(req_ref) = NpmPackageReqReference::from_str(value) { + entries.insert(JsrDepPackageReq::npm(req_ref.into_inner().req)); + } + } + entries +} + +#[derive(Debug, Clone)] +pub enum SpecifierUnfurlerDiagnostic { + UnanalyzableDynamicImport { + specifier: ModuleSpecifier, + text_info: SourceTextInfo, + range: SourceRange, + }, +} + +impl SpecifierUnfurlerDiagnostic { + pub fn code(&self) -> &'static str { + match self { + Self::UnanalyzableDynamicImport { .. } => "unanalyzable-dynamic-import", + } + } + + pub fn message(&self) -> &'static str { + match self { + Self::UnanalyzableDynamicImport { .. } => { + "unable to analyze dynamic import" + } + } + } +} + +pub struct SpecifierUnfurler<'a> { + mapped_resolver: &'a MappedSpecifierResolver, + sloppy_imports_resolver: Option<&'a SloppyImportsResolver>, + bare_node_builtins: bool, +} + +impl<'a> SpecifierUnfurler<'a> { + pub fn new( + mapped_resolver: &'a MappedSpecifierResolver, + sloppy_imports_resolver: Option<&'a SloppyImportsResolver>, + bare_node_builtins: bool, + ) -> Self { + Self { + mapped_resolver, + sloppy_imports_resolver, + bare_node_builtins, + } + } + + fn unfurl_specifier( + &self, + referrer: &ModuleSpecifier, + specifier: &str, + ) -> Option<String> { + let resolved = + if let Ok(resolved) = self.mapped_resolver.resolve(specifier, referrer) { + resolved.into_specifier() + } else { + None + }; + let resolved = match resolved { + Some(resolved) => resolved, + None if self.bare_node_builtins && is_builtin_node_module(specifier) => { + format!("node:{specifier}").parse().unwrap() + } + None => ModuleSpecifier::options() + .base_url(Some(referrer)) + .parse(specifier) + .ok()?, + }; + // TODO(lucacasonato): this requires integration in deno_graph first + // let resolved = if let Ok(specifier) = + // NpmPackageReqReference::from_specifier(&resolved) + // { + // if let Some(scope_name) = specifier.req().name.strip_prefix("@jsr/") { + // let (scope, name) = scope_name.split_once("__")?; + // let new_specifier = JsrPackageReqReference::new(PackageReqReference { + // req: PackageReq { + // name: format!("@{scope}/{name}"), + // version_req: specifier.req().version_req.clone(), + // }, + // sub_path: specifier.sub_path().map(ToOwned::to_owned), + // }) + // .to_string(); + // ModuleSpecifier::parse(&new_specifier).unwrap() + // } else { + // resolved + // } + // } else { + // resolved + // }; + let resolved = + if let Some(sloppy_imports_resolver) = self.sloppy_imports_resolver { + sloppy_imports_resolver + .resolve(&resolved) + .as_specifier() + .clone() + } else { + resolved + }; + relative_url(&resolved, referrer, specifier) + } + + /// Attempts to unfurl the dynamic dependency returning `true` on success + /// or `false` when the import was not analyzable. + fn try_unfurl_dynamic_dep( + &self, + module_url: &lsp_types::Url, + parsed_source: &ParsedSource, + dep: &deno_graph::DynamicDependencyDescriptor, + text_changes: &mut Vec<deno_ast::TextChange>, + ) -> bool { + match &dep.argument { + deno_graph::DynamicArgument::String(specifier) => { + let range = to_range(parsed_source, &dep.argument_range); + let maybe_relative_index = + parsed_source.text_info().text_str()[range.start..].find(specifier); + let Some(relative_index) = maybe_relative_index else { + return false; + }; + let unfurled = self.unfurl_specifier(module_url, specifier); + let Some(unfurled) = unfurled else { + return false; + }; + let start = range.start + relative_index; + text_changes.push(deno_ast::TextChange { + range: start..start + specifier.len(), + new_text: unfurled, + }); + true + } + deno_graph::DynamicArgument::Template(parts) => match parts.first() { + Some(DynamicTemplatePart::String { value: specifier }) => { + // relative doesn't need to be modified + let is_relative = + specifier.starts_with("./") || specifier.starts_with("../"); + if is_relative { + return true; + } + if !specifier.ends_with('/') { + return false; + } + let unfurled = self.unfurl_specifier(module_url, specifier); + let Some(unfurled) = unfurled else { + return false; + }; + let range = to_range(parsed_source, &dep.argument_range); + let maybe_relative_index = + parsed_source.text_info().text_str()[range.start..].find(specifier); + let Some(relative_index) = maybe_relative_index else { + return false; + }; + let start = range.start + relative_index; + text_changes.push(deno_ast::TextChange { + range: start..start + specifier.len(), + new_text: unfurled, + }); + true + } + Some(DynamicTemplatePart::Expr) => { + false // failed analyzing + } + None => { + true // ignore + } + }, + deno_graph::DynamicArgument::Expr => { + false // failed analyzing + } + } + } + + pub fn unfurl( + &self, + url: &ModuleSpecifier, + parsed_source: &ParsedSource, + diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic), + ) -> String { + let mut text_changes = Vec::new(); + let module_info = DefaultModuleAnalyzer::module_info(parsed_source); + let analyze_specifier = + |specifier: &str, + range: &deno_graph::PositionRange, + text_changes: &mut Vec<deno_ast::TextChange>| { + if let Some(unfurled) = self.unfurl_specifier(url, specifier) { + text_changes.push(deno_ast::TextChange { + range: to_range(parsed_source, range), + new_text: unfurled, + }); + } + }; + for dep in &module_info.dependencies { + match dep { + DependencyDescriptor::Static(dep) => { + analyze_specifier( + &dep.specifier, + &dep.specifier_range, + &mut text_changes, + ); + } + DependencyDescriptor::Dynamic(dep) => { + let success = self.try_unfurl_dynamic_dep( + url, + parsed_source, + dep, + &mut text_changes, + ); + + if !success { + let start_pos = parsed_source + .text_info() + .line_start(dep.argument_range.start.line) + + dep.argument_range.start.character; + let end_pos = parsed_source + .text_info() + .line_start(dep.argument_range.end.line) + + dep.argument_range.end.character; + diagnostic_reporter( + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { + specifier: url.to_owned(), + range: SourceRange::new(start_pos, end_pos), + text_info: parsed_source.text_info().clone(), + }, + ); + } + } + } + } + for ts_ref in &module_info.ts_references { + let specifier_with_range = match ts_ref { + TypeScriptReference::Path(range) => range, + TypeScriptReference::Types(range) => range, + }; + analyze_specifier( + &specifier_with_range.text, + &specifier_with_range.range, + &mut text_changes, + ); + } + for specifier_with_range in &module_info.jsdoc_imports { + analyze_specifier( + &specifier_with_range.text, + &specifier_with_range.range, + &mut text_changes, + ); + } + if let Some(specifier_with_range) = &module_info.jsx_import_source { + analyze_specifier( + &specifier_with_range.text, + &specifier_with_range.range, + &mut text_changes, + ); + } + + let rewritten_text = deno_ast::apply_text_changes( + parsed_source.text_info().text_str(), + text_changes, + ); + rewritten_text + } +} + +fn relative_url( + resolved: &ModuleSpecifier, + referrer: &ModuleSpecifier, + specifier: &str, +) -> Option<String> { + let new_specifier = if resolved.scheme() == "file" { + format!("./{}", referrer.make_relative(resolved).unwrap()) + } else { + resolved.to_string() + }; + if new_specifier == specifier { + return None; + } + Some(new_specifier) +} + +fn to_range( + parsed_source: &ParsedSource, + range: &deno_graph::PositionRange, +) -> std::ops::Range<usize> { + let mut range = range + .as_source_range(parsed_source.text_info()) + .as_byte_range(parsed_source.text_info().range().start); + let text = &parsed_source.text_info().text_str()[range.clone()]; + if text.starts_with('"') || text.starts_with('\'') { + range.start += 1; + } + if text.ends_with('"') || text.ends_with('\'') { + range.end -= 1; + } + range +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use crate::args::package_json::get_local_package_json_version_reqs; + use crate::args::PackageJsonDepsProvider; + + use super::*; + use deno_ast::MediaType; + use deno_ast::ModuleSpecifier; + use deno_core::serde_json::json; + use deno_core::url::Url; + use deno_runtime::deno_fs::RealFs; + use deno_runtime::deno_node::PackageJson; + use import_map::ImportMapWithDiagnostics; + use indexmap::IndexMap; + use pretty_assertions::assert_eq; + use test_util::testdata_path; + + fn parse_ast(specifier: &Url, source_code: &str) -> ParsedSource { + let media_type = MediaType::from_specifier(specifier); + deno_ast::parse_module(deno_ast::ParseParams { + specifier: specifier.clone(), + media_type, + capture_tokens: false, + maybe_syntax: None, + scope_analysis: false, + text_info: deno_ast::SourceTextInfo::new(source_code.into()), + }) + .unwrap() + } + + #[test] + fn test_unfurling() { + let cwd = testdata_path().join("unfurl").to_path_buf(); + + let deno_json_url = + ModuleSpecifier::from_file_path(cwd.join("deno.json")).unwrap(); + let value = json!({ + "imports": { + "express": "npm:express@5", + "lib/": "./lib/", + "fizz": "./fizz/mod.ts", + "@std/fs": "npm:@jsr/std__fs@1", + } + }); + let ImportMapWithDiagnostics { import_map, .. } = + import_map::parse_from_value(deno_json_url, value).unwrap(); + let mut package_json = PackageJson::empty(cwd.join("package.json")); + package_json.dependencies = + Some(IndexMap::from([("chalk".to_string(), "5".to_string())])); + let mapped_resolver = MappedSpecifierResolver::new( + Some(Arc::new(import_map)), + Arc::new(PackageJsonDepsProvider::new(Some( + get_local_package_json_version_reqs(&package_json), + ))), + ); + + let fs = Arc::new(RealFs); + let sloppy_imports_resolver = SloppyImportsResolver::new(fs); + + let unfurler = SpecifierUnfurler::new( + &mapped_resolver, + Some(&sloppy_imports_resolver), + true, + ); + + // Unfurling TS file should apply changes. + { + let source_code = r#"import express from "express";" +import foo from "lib/foo.ts"; +import bar from "lib/bar.ts"; +import fizz from "fizz"; +import chalk from "chalk"; +import baz from "./baz"; +import b from "./b.js"; +import b2 from "./b"; +import url from "url"; +// TODO: unfurl these to jsr +// import "npm:@jsr/std__fs@1/file"; +// import "npm:@jsr/std__fs@1"; +// import "npm:@jsr/std__fs"; +// import "@std/fs"; + +const test1 = await import("lib/foo.ts"); +const test2 = await import(`lib/foo.ts`); +const test3 = await import(`lib/${expr}`); +const test4 = await import(`./lib/${expr}`); +// will warn +const test5 = await import(`lib${expr}`); +const test6 = await import(`${expr}`); +"#; + let specifier = + ModuleSpecifier::from_file_path(cwd.join("mod.ts")).unwrap(); + let source = parse_ast(&specifier, source_code); + let mut d = Vec::new(); + let mut reporter = |diagnostic| d.push(diagnostic); + let unfurled_source = unfurler.unfurl(&specifier, &source, &mut reporter); + assert_eq!(d.len(), 2); + assert!( + matches!( + d[0], + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } + ), + "{:?}", + d[0] + ); + assert!( + matches!( + d[1], + SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } + ), + "{:?}", + d[1] + ); + let expected_source = r#"import express from "npm:express@5";" +import foo from "./lib/foo.ts"; +import bar from "./lib/bar.ts"; +import fizz from "./fizz/mod.ts"; +import chalk from "npm:chalk@5"; +import baz from "./baz/index.js"; +import b from "./b.ts"; +import b2 from "./b.ts"; +import url from "node:url"; +// TODO: unfurl these to jsr +// import "npm:@jsr/std__fs@1/file"; +// import "npm:@jsr/std__fs@1"; +// import "npm:@jsr/std__fs"; +// import "@std/fs"; + +const test1 = await import("./lib/foo.ts"); +const test2 = await import(`./lib/foo.ts`); +const test3 = await import(`./lib/${expr}`); +const test4 = await import(`./lib/${expr}`); +// will warn +const test5 = await import(`lib${expr}`); +const test6 = await import(`${expr}`); +"#; + assert_eq!(unfurled_source, expected_source); + } + } +} |