From ce16f8668ea2cf59bc6a9e3078f714b17fdd38a4 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 11 Apr 2024 10:29:37 +0200 Subject: [PATCH 1/8] feat: redo how we transpile JSR packages for NPM This fixes various bugs, and introduces some features. Specifically: - Source code is now included in the bundle to enable better "Go to source definition" - More usecases using `/// ` work correctly now, especially when d.ts file is not a sibling of the JS file. - We strip `/// ` out of JS files correctly now. - We transpile `npm:` and `jsr:` specifiers in user provided `.d.ts` files now. --- api/src/analysis.rs | 4 +- api/src/npm/emit.rs | 246 +++----- api/src/npm/import_transform.rs | 97 ++++ api/src/npm/mod.rs | 5 +- api/src/npm/specifiers.rs | 202 +++++-- api/src/npm/tarball.rs | 536 ++++++++++++------ api/src/npm/types.rs | 10 +- .../specs/npm_tarballs/additional_files.txt | 17 +- .../specs/npm_tarballs/import_jsr.txt | 59 +- .../specs/npm_tarballs/import_npm.txt | 59 +- .../specs/npm_tarballs/js_with_dts.txt | 83 +++ .../specs/npm_tarballs/jsdoc_import.txt | 65 +++ api/testdata/specs/npm_tarballs/transpile.txt | 22 +- .../npm_tarballs/transpile_with_imports.txt | 69 +++ 14 files changed, 1075 insertions(+), 399 deletions(-) create mode 100644 api/src/npm/import_transform.rs create mode 100644 api/testdata/specs/npm_tarballs/js_with_dts.txt create mode 100644 api/testdata/specs/npm_tarballs/jsdoc_import.txt create mode 100644 api/testdata/specs/npm_tarballs/transpile_with_imports.txt diff --git a/api/src/analysis.rs b/api/src/analysis.rs index e13e6d17..2c363b41 100644 --- a/api/src/analysis.rs +++ b/api/src/analysis.rs @@ -242,7 +242,7 @@ async fn analyze_package_inner( let npm_tarball = create_npm_tarball(NpmTarballOptions { graph: &graph, - sources: &module_analyzer.analyzer, + analyzer: &module_analyzer.analyzer, registry_url: registry.registry_url(), scope: &scope, package: &name, @@ -661,7 +661,7 @@ async fn rebuild_npm_tarball_inner( let npm_tarball = create_npm_tarball(NpmTarballOptions { graph: &graph, - sources: &module_analyzer.analyzer, + analyzer: &module_analyzer.analyzer, registry_url: registry.registry_url(), scope: &scope, package: &name, diff --git a/api/src/npm/emit.rs b/api/src/npm/emit.rs index 82bdb926..fc765f63 100644 --- a/api/src/npm/emit.rs +++ b/api/src/npm/emit.rs @@ -2,215 +2,101 @@ use deno_ast::emit; use deno_ast::fold_program; -use deno_ast::swc::ast::CallExpr; -use deno_ast::swc::ast::Callee; -use deno_ast::swc::ast::ExportAll; -use deno_ast::swc::ast::Expr; -use deno_ast::swc::ast::ExprOrSpread; -use deno_ast::swc::ast::ImportDecl; -use deno_ast::swc::ast::Lit; -use deno_ast::swc::ast::Module; -use deno_ast::swc::ast::NamedExport; -use deno_ast::swc::ast::Str; use deno_ast::swc::common::Globals; use deno_ast::swc::common::Mark; -use deno_ast::swc::visit::as_folder; -use deno_ast::swc::visit::noop_visit_mut_type; -use deno_ast::swc::visit::FoldWith; -use deno_ast::swc::visit::VisitMut; use deno_ast::swc::visit::VisitMutWith; +use deno_ast::EmittedSource; use deno_ast::ParsedSource; use deno_ast::SourceMap; use deno_ast::SourceMapOption; -use url::Url; +use deno_ast::TranspileOptions; +use deno_graph::FastCheckTypeModule; -use super::specifiers::rewrite_specifier; +use crate::npm::import_transform::ImportRewriteTransformer; -// todo: a lot of code is duplicated with `ParsedSource::transpile` because we -// can't fold a `ParsedSource` directly. -pub fn transpile_to_js( - source: ParsedSource, - source_url: Url, +use super::specifiers::RewriteKind; +use super::specifiers::SpecifierRewriter; + +pub fn transpile_to_js<'a>( + source: &ParsedSource, + specifier_rewriter: SpecifierRewriter<'a>, ) -> Result { - let transpile_options = deno_ast::TranspileOptions { - // FIXME: JSX? - ..Default::default() - }; let emit_options = deno_ast::EmitOptions { source_map: SourceMapOption::Inline, - inline_sources: true, + inline_sources: false, keep_comments: true, }; + let file_name = source.specifier().path().split('/').last().unwrap(); let source_map = - SourceMap::single(source_url, source.text_info().text_str().to_string()); + SourceMap::single(file_name, source.text_info().text_str().to_owned()); - let mut folder = as_folder(NpmImportTransform); - let program = source.program_ref().clone().fold_with(&mut folder); + let mut program = source.program_ref().clone(); + + let mut import_rewrite_transformer = ImportRewriteTransformer { + specifier_rewriter, + kind: RewriteKind::Source, + }; + program.visit_mut_with(&mut import_rewrite_transformer); - // needs to align with what's done internally in source map - assert_eq!(1, source.text_info().range().start.as_byte_pos().0); - // we need the comments to be mutable, so make it single threaded let comments = source.comments().as_single_threaded(); + + let transpile_options = TranspileOptions { + use_decorators_proposal: true, + use_ts_decorators: false, + + // TODO: JSX + ..Default::default() + }; + let globals = Globals::new(); - deno_ast::swc::common::GLOBALS.set(&globals, || { + let program = deno_ast::swc::common::GLOBALS.set(&globals, || { let top_level_mark = Mark::fresh(Mark::root()); - let program = fold_program( + + fold_program( program, &transpile_options, &source_map, &comments, top_level_mark, source.diagnostics(), - )?; + ) + })?; - let emitted = emit(&program, &comments, &source_map, &emit_options)?; + let emitted = emit(&program, &comments, &source_map, &emit_options)?; - Ok(emitted.text) - }) + Ok(emitted.text) } -pub struct NpmImportTransform; - -impl VisitMut for NpmImportTransform { - noop_visit_mut_type!(); - - fn visit_mut_module(&mut self, module: &mut Module) { - module.visit_mut_children_with(self); - } - - fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) { - node.visit_mut_children_with(self); - - if let Some(remapped) = rewrite_specifier(&node.src.value) { - node.src = Box::new(remapped.into()); - } - } - - fn visit_mut_named_export(&mut self, node: &mut NamedExport) { - node.visit_mut_children_with(self); - - if let Some(src) = &node.src { - if let Some(remapped) = rewrite_specifier(&src.value) { - node.src = Some(Box::new(remapped.into())); - } - } - } - - fn visit_mut_export_all(&mut self, node: &mut ExportAll) { - node.visit_mut_children_with(self); - - if let Some(remapped) = rewrite_specifier(&node.src.value) { - node.src = Box::new(remapped.into()); - } - } - - fn visit_mut_call_expr(&mut self, node: &mut CallExpr) { - node.visit_mut_children_with(self); - - if let Callee::Import(_) = node.callee { - if let Some(arg) = node.args.first() { - if let Expr::Lit(Lit::Str(lit_str)) = *arg.expr.clone() { - let maybe_rewritten = rewrite_specifier(&lit_str.value); - if let Some(rewritten) = maybe_rewritten { - let replacer = Expr::Lit(Lit::Str(Str { - span: lit_str.span, - value: rewritten.into(), - raw: None, - })); - node.args[0] = ExprOrSpread { - spread: None, - expr: Box::new(replacer), - }; - } - } - } - } - } -} +pub fn transpile_to_dts<'a>( + source: &ParsedSource, + fast_check_module: &FastCheckTypeModule, + specifier_rewriter: SpecifierRewriter<'a>, +) -> Result { + let dts = fast_check_module.dts.as_ref().unwrap(); + + let emit_options = deno_ast::EmitOptions { + source_map: SourceMapOption::Inline, + inline_sources: false, + keep_comments: true, + }; + + let file_name = source.specifier().path().split('/').last().unwrap(); + let source_map = + SourceMap::single(file_name, source.text_info().text_str().to_owned()); + + let mut program = dts.program.clone(); + + let mut import_rewrite_transformer = ImportRewriteTransformer { + specifier_rewriter, + kind: RewriteKind::Declaration, + }; + program.visit_mut_with(&mut import_rewrite_transformer); + + let comments = dts.comments.as_single_threaded(); + + let EmittedSource { text, .. } = + emit(&program, &comments, &source_map, &emit_options)?; -#[cfg(test)] -mod tests { - use deno_ast::ModuleSpecifier; - use deno_ast::ParseParams; - use deno_ast::SourceTextInfo; - - use super::transpile_to_js; - - fn test_transform(source: &str, expect: &str) { - let specifier = "file:///main.ts"; - - let parsed_source = deno_ast::parse_module(ParseParams { - specifier: ModuleSpecifier::parse(specifier).unwrap(), - media_type: deno_ast::MediaType::TypeScript, - text_info: SourceTextInfo::new(source.into()), - capture_tokens: false, - scope_analysis: false, - maybe_syntax: None, - }) - .unwrap(); - - let result = - transpile_to_js(parsed_source, specifier.parse().unwrap()).unwrap(); - let (result, _) = result - .rsplit_once("\n//# sourceMappingURL=data:application/json;base64,") - .unwrap(); - - assert_eq!(result.trim_end(), expect); - } - - #[test] - fn test_transform_specifiers() { - test_transform(r#"import "./foo/bar.ts";"#, r#"import "./foo/bar.js";"#); - test_transform(r#"import "../foo.tsx";"#, r#"import "../foo.js";"#); - test_transform(r#"import "jsr:@std/path";"#, r#"import "@jsr/std__path";"#); - test_transform(r#"import "npm:@std/path";"#, r#"import "@std/path";"#); - - test_transform( - "import * as foo from \"./bar.ts\";\nexport { foo };", - "import * as foo from \"./bar.js\";\nexport { foo };", - ); - test_transform( - "import { asd } from \"./bar.ts\";\nexport { asd };", - "import { asd } from \"./bar.js\";\nexport { asd };", - ); - test_transform( - "import { asd as foo } from \"./bar.ts\";\nexport { foo };", - "import { asd as foo } from \"./bar.js\";\nexport { foo };", - ); - test_transform( - "import { asd, foo } from \"./bar.ts\";\nexport { asd, foo };", - "import { asd, foo } from \"./bar.js\";\nexport { asd, foo };", - ); - test_transform( - "import asd from \"./bar.ts\";\nexport { asd };", - "import asd from \"./bar.js\";\nexport { asd };", - ); - test_transform( - "import asd, { foo } from \"./bar.ts\";\nexport { asd, foo };", - "import asd, { foo } from \"./bar.js\";\nexport { asd, foo };", - ); - - test_transform( - "export * from \"./foo/bar.ts\";", - "export * from \"./foo/bar.js\";", - ); - test_transform( - "export * as foo from \"./foo/bar.ts\";", - "export * as foo from \"./foo/bar.js\";", - ); - test_transform( - "export { asd } from \"./foo/bar.ts\";", - "export { asd } from \"./foo/bar.js\";", - ); - test_transform( - "export { asd as foo } from \"./foo/bar.ts\";", - "export { asd as foo } from \"./foo/bar.js\";", - ); - - test_transform( - "await import(\"./foo/bar.ts\");", - "await import(\"./foo/bar.js\");", - ); - } + Ok(text) } diff --git a/api/src/npm/import_transform.rs b/api/src/npm/import_transform.rs new file mode 100644 index 00000000..fa9eb9be --- /dev/null +++ b/api/src/npm/import_transform.rs @@ -0,0 +1,97 @@ +use deno_ast::swc::ast::CallExpr; +use deno_ast::swc::ast::Callee; +use deno_ast::swc::ast::ExportAll; +use deno_ast::swc::ast::Expr; +use deno_ast::swc::ast::ExprOrSpread; +use deno_ast::swc::ast::ImportDecl; +use deno_ast::swc::ast::Lit; +use deno_ast::swc::ast::Module; +use deno_ast::swc::ast::NamedExport; +use deno_ast::swc::ast::Str; +use deno_ast::swc::ast::TsImportType; +use deno_ast::swc::visit::VisitMut; +use deno_ast::swc::visit::VisitMutWith; + +use super::specifiers::RewriteKind; +use super::specifiers::SpecifierRewriter; + +pub struct ImportRewriteTransformer<'a> { + pub specifier_rewriter: SpecifierRewriter<'a>, + pub kind: RewriteKind, +} + +impl<'a> VisitMut for ImportRewriteTransformer<'a> { + fn visit_mut_module(&mut self, module: &mut Module) { + module.visit_mut_children_with(self); + } + + fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) { + node.visit_mut_children_with(self); + + if let Some(remapped) = self + .specifier_rewriter + .rewrite(&node.src.value.as_str(), self.kind) + { + node.src = Box::new(remapped.into()); + } + } + + fn visit_mut_named_export(&mut self, node: &mut NamedExport) { + node.visit_mut_children_with(self); + + if let Some(src) = &node.src { + if let Some(remapped) = self + .specifier_rewriter + .rewrite(&src.value.as_str(), self.kind) + { + node.src = Some(Box::new(remapped.into())); + } + } + } + + fn visit_mut_export_all(&mut self, node: &mut ExportAll) { + node.visit_mut_children_with(self); + + if let Some(remapped) = self + .specifier_rewriter + .rewrite(&node.src.value.as_str(), self.kind) + { + node.src = Box::new(remapped.into()); + } + } + + fn visit_mut_ts_import_type(&mut self, n: &mut TsImportType) { + n.visit_mut_children_with(self); + + if let Some(remapped) = self + .specifier_rewriter + .rewrite(&n.arg.value.as_str(), RewriteKind::Declaration) + { + n.arg = remapped.into(); + } + } + + fn visit_mut_call_expr(&mut self, node: &mut CallExpr) { + node.visit_mut_children_with(self); + + if let Callee::Import(_) = node.callee { + if let Some(arg) = node.args.first() { + if let Expr::Lit(Lit::Str(lit_str)) = *arg.expr.clone() { + let maybe_rewritten = + self.specifier_rewriter.rewrite(&lit_str.value, self.kind); + if let Some(rewritten) = maybe_rewritten { + let replacer = Expr::Lit(Lit::Str(Str { + span: lit_str.span, + value: rewritten.into(), + raw: None, + })); + node.args[0] = ExprOrSpread { + spread: None, + expr: Box::new(replacer), + }; + } + } + } + } + } +} diff --git a/api/src/npm/mod.rs b/api/src/npm/mod.rs index 99ecbdf1..92a37015 100644 --- a/api/src/npm/mod.rs +++ b/api/src/npm/mod.rs @@ -1,10 +1,11 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. -mod emit; mod specifiers; mod tarball; #[cfg(test)] mod tests; mod types; +mod import_transform; +mod emit; use chrono::SecondsFormat; use deno_semver::package::PackageReq; @@ -28,7 +29,7 @@ pub use self::tarball::NpmTarballOptions; pub use self::types::NpmMappedJsrPackageName; use self::types::NpmVersionInfo; -pub const NPM_TARBALL_REVISION: u32 = 7; +pub const NPM_TARBALL_REVISION: u32 = 8; pub async fn generate_npm_version_manifest<'a>( db: &Database, diff --git a/api/src/npm/specifiers.rs b/api/src/npm/specifiers.rs index b1f6e030..2d6e1cf3 100644 --- a/api/src/npm/specifiers.rs +++ b/api/src/npm/specifiers.rs @@ -1,12 +1,105 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. + +use std::collections::HashMap; + +use deno_ast::ModuleSpecifier; +use deno_graph::Dependency; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; +use indexmap::IndexMap; use crate::ids::ScopedPackageName; use super::NpmMappedJsrPackageName; -pub fn rewrite_specifier(specifier: &str) -> Option { +#[derive(Clone, Copy)] +pub enum RewriteKind { + Source, + Declaration, +} + +#[derive(Clone, Copy)] +pub struct SpecifierRewriter<'a> { + pub base_specifier: &'a ModuleSpecifier, + pub source_rewrites: &'a HashMap<&'a ModuleSpecifier, ModuleSpecifier>, + pub declaration_rewrites: &'a HashMap<&'a ModuleSpecifier, ModuleSpecifier>, + pub dependencies: &'a IndexMap, +} + +impl<'a> SpecifierRewriter<'a> { + pub fn rewrite(&self, specifier: &str, kind: RewriteKind) -> Option { + let dep = self.dependencies.get(specifier)?; + + let specifier = match kind { + RewriteKind::Source => dep.get_code(), + RewriteKind::Declaration => dep.get_type().or_else(|| dep.get_code()), + }?; + + let rewrites = match kind { + RewriteKind::Source => self.source_rewrites, + RewriteKind::Declaration => self.declaration_rewrites, + }; + + let resolved_specifier = follow_specifier(specifier, rewrites)?; + + if let Some(specifier) = + rewrite_npm_and_jsr_specifier(resolved_specifier.as_str()) + { + return Some(specifier); + }; + + if resolved_specifier == specifier { + // No need to rewrite if the specifier is the same as the resolved + // specifier. + return None; + } + + let new_specifier = if resolved_specifier.scheme() == "file" { + relative_import_specifier(self.base_specifier, resolved_specifier) + } else { + resolved_specifier.to_string() + }; + + Some(new_specifier) + } +} + +fn relative_import_specifier( + base_specifier: &ModuleSpecifier, + specifier: &ModuleSpecifier, +) -> String { + let relative = base_specifier.make_relative(&specifier).unwrap(); + if relative.is_empty() { + format!("./{}", specifier.path_segments().unwrap().last().unwrap()) + } else if relative.starts_with("../") { + relative.to_string() + } else { + format!("./{}", relative) + } +} + +fn follow_specifier<'a>( + specifier: &'a ModuleSpecifier, + remapped_specifiers: &'a HashMap<&ModuleSpecifier, ModuleSpecifier>, +) -> Option<&'a ModuleSpecifier> { + let mut redirects = 0; + let mut types_specifier = specifier; + loop { + // avoid infinite loops + if redirects > 10 { + return None; + } + if let Some(rewritten) = remapped_specifiers.get(&types_specifier) { + types_specifier = rewritten; + } else { + break; + } + redirects += 1; + } + Some(types_specifier) +} + +pub fn rewrite_npm_and_jsr_specifier(specifier: &str) -> Option { if let Ok(jsr) = JsrPackageReqReference::from_str(specifier) { let req = jsr.into_inner(); let jsr_name = ScopedPackageName::new(req.req.name).ok()?; @@ -38,8 +131,6 @@ pub fn rewrite_specifier(specifier: &str) -> Option { } ); Some(rewritten) - } else if specifier.starts_with("./") || specifier.starts_with("../") { - rewrite_extension(specifier, Extension::Js) } else { None } @@ -51,19 +142,36 @@ pub enum Extension { Dts, } -pub fn rewrite_extension(path: &str, new_ext: Extension) -> Option { +pub fn rewrite_file_specifier_extension( + specifier: &ModuleSpecifier, + new_extension: Extension, +) -> Option { + assert_eq!(specifier.scheme(), "file"); + let path = specifier.path(); + let rewritten_path = rewrite_path_extension(path, new_extension)?; + Some(ModuleSpecifier::parse(&format!("file://{}", rewritten_path)).unwrap()) +} + +pub fn rewrite_path_extension( + path: &str, + new_extension: Extension, +) -> Option { let (basename, name) = path.rsplit_once('/')?; - if name.ends_with(".d.ts") { + let (name, ext) = if let Some(name) = name.strip_suffix(".d.ts") { + (name, "d.ts") + } else if let Some(name) = name.strip_suffix(".d.mts") { + (name, "d.mts") + } else { + name.rsplit_once('.')? + }; + let new_ext = match new_extension { + Extension::Js => "js", + Extension::Dts => "d.ts", + }; + if ext == new_ext { return None; } - let (name, ext) = name.rsplit_once('.')?; - match new_ext { - Extension::Js => match ext { - "ts" | "tsx" | "jsx" => Some(format!("{}/{}.js", basename, name)), - _ => None, - }, - Extension::Dts => Some(format!("{}/{}.d.ts", basename, name)), - } + Some(format!("{}/{}.{}", basename, name, new_ext)) } #[cfg(test)] @@ -73,68 +181,94 @@ mod tests { #[test] fn test_rewrite_specifier_jsr() { assert_eq!( - rewrite_specifier("jsr:@std/fs"), + rewrite_npm_and_jsr_specifier("jsr:@std/fs"), Some("@jsr/std__fs".to_owned()) ); assert_eq!( - rewrite_specifier("jsr:@std/fs/file_server"), + rewrite_npm_and_jsr_specifier("jsr:@std/fs/file_server"), Some("@jsr/std__fs/file_server".to_owned()) ); assert_eq!( - rewrite_specifier("jsr:@std/fs@0.0.1"), + rewrite_npm_and_jsr_specifier("jsr:@std/fs@0.0.1"), Some("@jsr/std__fs".to_owned()) ); assert_eq!( - rewrite_specifier("jsr:@std/fs@0.0.1/file_server"), + rewrite_npm_and_jsr_specifier("jsr:@std/fs@0.0.1/file_server"), Some("@jsr/std__fs/file_server".to_owned()) ); } #[test] fn test_rewrite_specifier_npm() { - assert_eq!(rewrite_specifier("npm:@std/fs"), Some("@std/fs".to_owned())); assert_eq!( - rewrite_specifier("npm:@std/fs/file_server"), + rewrite_npm_and_jsr_specifier("npm:@std/fs"), + Some("@std/fs".to_owned()) + ); + assert_eq!( + rewrite_npm_and_jsr_specifier("npm:@std/fs/file_server"), Some("@std/fs/file_server".to_owned()) ); assert_eq!( - rewrite_specifier("npm:@std/fs@0.0.1"), + rewrite_npm_and_jsr_specifier("npm:@std/fs@0.0.1"), Some("@std/fs".to_owned()) ); assert_eq!( - rewrite_specifier("npm:@std/fs@0.0.1/file_server"), + rewrite_npm_and_jsr_specifier("npm:@std/fs@0.0.1/file_server"), Some("@std/fs/file_server".to_owned()) ); - assert_eq!(rewrite_specifier("npm:express"), Some("express".to_owned())); assert_eq!( - rewrite_specifier("npm:express/file_server"), + rewrite_npm_and_jsr_specifier("npm:express"), + Some("express".to_owned()) + ); + assert_eq!( + rewrite_npm_and_jsr_specifier("npm:express/file_server"), Some("express/file_server".to_owned()) ); assert_eq!( - rewrite_specifier("npm:express@0.0.1"), + rewrite_npm_and_jsr_specifier("npm:express@0.0.1"), Some("express".to_owned()) ); assert_eq!( - rewrite_specifier("npm:express@0.0.1/file_server"), + rewrite_npm_and_jsr_specifier("npm:express@0.0.1/file_server"), Some("express/file_server".to_owned()) ); } #[test] - fn test_rewrite_specifier_relative() { + fn test_rewrite_path_extension() { + assert_eq!( + rewrite_path_extension("foo/bar.ts", Extension::Js), + Some("foo/bar.js".to_owned()) + ); + assert_eq!( + rewrite_path_extension("foo/bar.ts", Extension::Dts), + Some("foo/bar.d.ts".to_owned()) + ); + assert_eq!( + rewrite_path_extension("foo/bar.d.ts", Extension::Js), + Some("foo/bar.js".to_owned()) + ); + assert_eq!(rewrite_path_extension("foo/bar.d.ts", Extension::Dts), None); + assert_eq!( + rewrite_path_extension("foo/bar.d.mts", Extension::Js), + Some("foo/bar.js".to_owned()) + ); + assert_eq!( + rewrite_path_extension("foo/bar.d.mts", Extension::Dts), + Some("foo/bar.d.ts".to_owned()) + ); + assert_eq!(rewrite_path_extension("foo/bar.js", Extension::Js), None); assert_eq!( - rewrite_specifier("./foo/bar.ts"), - Some("./foo/bar.js".to_owned()) + rewrite_path_extension("foo/bar.js", Extension::Dts), + Some("foo/bar.d.ts".to_owned()) ); assert_eq!( - rewrite_specifier("../foo.tsx"), - Some("../foo.js".to_owned()) + rewrite_path_extension("foo/bar.jsx", Extension::Js), + Some("foo/bar.js".to_owned()) ); assert_eq!( - rewrite_specifier("../foo.jsx"), - Some("../foo.js".to_owned()) + rewrite_path_extension("foo/bar.jsx", Extension::Dts), + Some("foo/bar.d.ts".to_owned()) ); - assert_eq!(rewrite_specifier("./foo.js"), None); - assert_eq!(rewrite_specifier("./foo.d.ts"), None); } } diff --git a/api/src/npm/tarball.rs b/api/src/npm/tarball.rs index 27d2a451..ee612a09 100644 --- a/api/src/npm/tarball.rs +++ b/api/src/npm/tarball.rs @@ -3,19 +3,19 @@ use std::borrow::Cow; use std::collections::HashMap; use std::collections::HashSet; -use anyhow::Context; use base64::Engine; use deno_ast::apply_text_changes; -use deno_ast::emit; -use deno_ast::EmitOptions; -use deno_ast::ParsedSource; -use deno_ast::SourceMap; +use deno_ast::SourceTextInfo; use deno_ast::TextChange; +use deno_graph::CapturingModuleAnalyzer; use deno_graph::DependencyDescriptor; +use deno_graph::ModuleAnalyzer; use deno_graph::ModuleGraph; +use deno_graph::ModuleInfo; +use deno_graph::ModuleSpecifier; use deno_graph::ParsedSourceStore; -use deno_graph::ParserModuleAnalyzer; use deno_graph::PositionRange; +use deno_graph::Resolution; use deno_semver::package::PackageReqReference; use futures::StreamExt; use futures::TryStreamExt; @@ -23,7 +23,6 @@ use indexmap::IndexMap; use sha2::Digest; use tar::Header; use tracing::error; -use tracing::info; use url::Url; use crate::buckets::BucketWithQueue; @@ -34,13 +33,16 @@ use crate::ids::PackagePath; use crate::ids::ScopeName; use crate::ids::ScopedPackageName; use crate::ids::Version; -use crate::npm::specifiers::rewrite_extension; -use crate::npm::specifiers::rewrite_specifier; -use crate::npm::specifiers::Extension; -use crate::npm::types::NpmMappedJsrPackageName; -use crate::npm::types::NpmPackageJson; +use super::emit::transpile_to_dts; use super::emit::transpile_to_js; +use super::specifiers::rewrite_file_specifier_extension; +use super::specifiers::Extension; +use super::specifiers::RewriteKind; +use super::specifiers::SpecifierRewriter; +use super::types::NpmExportConditions; +use super::types::NpmMappedJsrPackageName; +use super::types::NpmPackageJson; use super::NPM_TARBALL_REVISION; pub struct NpmTarball { @@ -65,7 +67,7 @@ pub struct NpmTarballOptions< Deps: Iterator, > { pub graph: &'a ModuleGraph, - pub sources: &'a dyn ParsedSourceStore, + pub analyzer: &'a CapturingModuleAnalyzer, pub registry_url: &'a Url, pub scope: &'a ScopeName, pub package: &'a PackageName, @@ -83,7 +85,7 @@ pub async fn create_npm_tarball<'a>( ) -> Result { let NpmTarballOptions { graph, - sources, + analyzer: sources, registry_url, scope, package, @@ -95,8 +97,6 @@ pub async fn create_npm_tarball<'a>( let npm_package_id = NpmMappedJsrPackageName { scope, package }; - let npm_exports = create_npm_exports(exports); - let npm_dependencies = create_npm_dependencies(dependencies.map(Cow::Borrowed))?; @@ -106,155 +106,167 @@ pub async fn create_npm_tarball<'a>( .unwrap() .to_string(); - let pkg_json = NpmPackageJson { - name: npm_package_id, - version: version.clone(), - module_type: "module".to_string(), - exports: npm_exports, - dependencies: npm_dependencies, - homepage, - revision: NPM_TARBALL_REVISION, - }; - - let mut transpiled_files = HashSet::new(); let mut package_files = IndexMap::new(); + let mut to_be_rewritten = vec![]; + + // Mapping of original specifiers in the module graph to where one can find + // the source code or declarations for that module in the tarball, if it + // differs from the original specifier. + let mut source_rewrites = HashMap::<&ModuleSpecifier, ModuleSpecifier>::new(); + let mut declaration_rewrites = + HashMap::<&ModuleSpecifier, ModuleSpecifier>::new(); for module in graph.modules() { if module.specifier().scheme() != "file" { continue; }; - let path = module.specifier().path(); - - if let Some(json) = module.json() { - package_files.insert(path.to_owned(), json.source.as_bytes().to_vec()); - } else if let Some(js) = module.js() { - match js.media_type { - // We need to rewrite import source in js files too - // from `npm:*` to bare specifiers, for example. - deno_ast::MediaType::JavaScript | deno_ast::MediaType::Mjs => { - let source = sources - .get_parsed_source(module.specifier()) - .expect("parsed source should be here"); - - let module_info = ParserModuleAnalyzer::module_info(&source); - - let maybe_rewrite_specifier = - |specifier: &str, - range: &PositionRange, - text_changes: &mut Vec| { - if let Some(rewritten) = rewrite_specifier(specifier) { - text_changes.push(TextChange { - new_text: rewritten, - range: to_range(&source, range), - }); - } - }; - - let mut text_changes = vec![]; - for dep in &module_info.dependencies { - match dep { - DependencyDescriptor::Static(dep) => { - maybe_rewrite_specifier( - &dep.specifier, - &dep.specifier_range, - &mut text_changes, - ); - } - DependencyDescriptor::Dynamic(dep) => match &dep.argument { - deno_graph::DynamicArgument::String(str_arg) => { - maybe_rewrite_specifier( - str_arg, - &dep.argument_range, - &mut text_changes, - ); - } - deno_graph::DynamicArgument::Template(_) => {} - deno_graph::DynamicArgument::Expr => {} - }, - } - } - let rewritten = - apply_text_changes(source.text_info().text_str(), text_changes); + let Some(js) = module.js() else { continue }; - package_files.insert(path.to_owned(), rewritten.as_bytes().to_vec()); + match js.media_type { + deno_ast::MediaType::JavaScript | deno_ast::MediaType::Mjs => { + if let Some(types_dep) = &js.maybe_types_dependency { + if let Resolution::Ok(resolved) = &types_dep.dependency { + declaration_rewrites + .insert(module.specifier(), resolved.specifier.clone()); + } } - deno_ast::MediaType::Dts | deno_ast::MediaType::Dmts => { - package_files.insert(path.to_owned(), js.source.as_bytes().to_vec()); + } + deno_ast::MediaType::Jsx => { + let source_specifier = + rewrite_file_specifier_extension(module.specifier(), Extension::Js); + if let Some(source_specifier) = source_specifier { + source_rewrites.insert(module.specifier(), source_specifier); } - deno_ast::MediaType::Jsx - | deno_ast::MediaType::TypeScript - | deno_ast::MediaType::Mts - | deno_ast::MediaType::Tsx => { - let source = sources - .get_parsed_source(module.specifier()) - .expect("parsed source should be here"); - let source_url = Url::options() - .base_url(Some(registry_url)) - .parse(&format!("./@{scope}/{package}/{version}{path}",)) - .unwrap(); - let transpiled = transpile_to_js(source, source_url) - .with_context(|| format!("failed to transpile {}", path))?; - - let rewritten_path = rewrite_extension(path, Extension::Js) - .unwrap_or_else(|| path.to_owned()); - transpiled_files.insert(path.to_owned()); - package_files.insert(rewritten_path, transpiled.as_bytes().to_vec()); + + if let Some(types_dep) = &js.maybe_types_dependency { + if let Resolution::Ok(resolved) = &types_dep.dependency { + declaration_rewrites + .insert(module.specifier(), resolved.specifier.clone()); + } } - _ => {} } + deno_ast::MediaType::Dts | deno_ast::MediaType::Dmts => { + // no extra work needed for these, as they can not have type dependencies + } + deno_ast::MediaType::TypeScript | deno_ast::MediaType::Mts => { + let source_specifier = + rewrite_file_specifier_extension(module.specifier(), Extension::Js); + if let Some(source_specifier) = source_specifier { + source_rewrites.insert(module.specifier(), source_specifier); + } - // Dts files - if let Some(fsm) = js.fast_check_module() { - if let Some(dts) = &fsm.dts { - if !dts.diagnostics.is_empty() { - let message = dts - .diagnostics - .iter() - .map(|d| match d.range() { - Some(range) => { - format!("{}, at {}@{}", d, range.specifier, range.range.start) - } - None => format!("{}, at {}", d, d.specifier()), - }) - .collect::>() - .join(", "); - info!( - "Npm dts generation @{}/{}@{}: {}", - scope, package, version, message - ); + if js.fast_check_module().is_some() { + let declaration_specifier = rewrite_file_specifier_extension( + module.specifier(), + Extension::Dts, + ); + if let Some(declaration_specifier) = declaration_specifier { + declaration_rewrites + .insert(module.specifier(), declaration_specifier); } + } + } + _ => {} + } + + to_be_rewritten.push(js); + } - let rewritten_path = rewrite_extension(path, Extension::Dts) - .unwrap_or_else(|| path.to_owned()); - let comments = dts.comments.as_single_threaded(); - let source_map = - SourceMap::single(js.specifier.clone(), (*js.source).to_owned()); - let emitted = emit( - &dts.program, - &comments, - &source_map, - &EmitOptions { - source_map: deno_ast::SourceMapOption::None, - inline_sources: true, - keep_comments: true, - }, + for js in to_be_rewritten { + let specifier_rewriter = SpecifierRewriter { + base_specifier: &js.specifier, + source_rewrites: &source_rewrites, + declaration_rewrites: &declaration_rewrites, + dependencies: &js.dependencies, + }; + + match js.media_type { + deno_ast::MediaType::JavaScript | deno_ast::MediaType::Mjs => { + let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); + let module_info = sources + .analyze(&js.specifier, js.source.clone(), js.media_type) + .unwrap(); + let rewritten = rewrite_specifiers( + parsed_source.text_info(), + &module_info, + specifier_rewriter, + RewriteKind::Source, + ); + package_files.insert( + js.specifier.path().to_owned(), + rewritten.as_bytes().to_vec(), + ); + } + deno_ast::MediaType::Dts | deno_ast::MediaType::Dmts => { + let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); + let module_info = sources + .analyze(&js.specifier, js.source.clone(), js.media_type) + .unwrap(); + let rewritten = rewrite_specifiers( + parsed_source.text_info(), + &module_info, + specifier_rewriter, + RewriteKind::Declaration, + ); + package_files.insert( + js.specifier.path().to_owned(), + rewritten.as_bytes().to_vec(), + ); + } + deno_ast::MediaType::Jsx => { + let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); + let source = + transpile_to_js(&parsed_source, specifier_rewriter).unwrap(); + let source_target = source_rewrites.get(&js.specifier).unwrap(); + package_files + .insert(source_target.path().to_owned(), source.into_bytes()); + } + deno_ast::MediaType::TypeScript | deno_ast::MediaType::Mts => { + let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); + let module_info = sources + .analyze(&js.specifier, js.source.clone(), js.media_type) + .unwrap(); + let rewritten = rewrite_specifiers( + parsed_source.text_info(), + &module_info, + specifier_rewriter, + RewriteKind::Source, + ); + package_files.insert( + js.specifier.path().to_owned(), + rewritten.as_bytes().to_vec(), + ); + + let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); + let source = + transpile_to_js(&parsed_source, specifier_rewriter).unwrap(); + let source_target = source_rewrites.get(&js.specifier).unwrap(); + package_files + .insert(source_target.path().to_owned(), source.into_bytes()); + + if let Some(fast_check_module) = js.fast_check_module() { + let declaration = transpile_to_dts( + &parsed_source, + fast_check_module, + specifier_rewriter, )?; - package_files.insert(rewritten_path, emitted.text.into_bytes()); + let declaration_target = + declaration_rewrites.get(&js.specifier).unwrap(); + package_files.insert( + declaration_target.path().to_owned(), + declaration.into_bytes(), + ); } } + _ => {} } } - let pkg_json_str = serde_json::to_string_pretty(&pkg_json)?; - package_files.insert("/package.json".to_string(), pkg_json_str.into()); - match files { NpmTarballFiles::WithBytes(files) => { for (path, content) in files.iter() { - if !package_files.contains_key(&**path) - && !transpiled_files.contains(&**path) - { + if !package_files.contains_key(&**path) { package_files.insert(path.to_string(), content.clone()); } } @@ -265,9 +277,7 @@ pub async fn create_npm_tarball<'a>( } => { let mut paths_to_download = vec![]; for path in files.iter() { - if !package_files.contains_key(&**path) - && !transpiled_files.contains(&**path) - { + if !package_files.contains_key(&**path) { paths_to_download.push(path); } } @@ -293,6 +303,26 @@ pub async fn create_npm_tarball<'a>( } } + let npm_exports = create_npm_exports( + exports, + &package_files, + &source_rewrites, + &declaration_rewrites, + ); + + let pkg_json = NpmPackageJson { + name: npm_package_id, + version: version.clone(), + module_type: "module".to_string(), + exports: npm_exports, + dependencies: npm_dependencies, + homepage, + revision: NPM_TARBALL_REVISION, + }; + + let pkg_json_str = serde_json::to_string_pretty(&pkg_json)?; + package_files.insert("/package.json".to_string(), pkg_json_str.into()); + package_files.sort_keys(); let mut tar_gz_bytes = Vec::new(); @@ -340,6 +370,124 @@ pub async fn create_npm_tarball<'a>( }) } +fn rewrite_specifiers<'a>( + source_text_info: &SourceTextInfo, + module_info: &ModuleInfo, + specifier_rewriter: SpecifierRewriter, + kind: RewriteKind, +) -> String { + let mut text_changes = vec![]; + + let add_text_change = |text_changes: &mut Vec, + new_specifier: String, + range: &PositionRange| { + let start_pos = source_text_info.range().start; + let mut start = range + .start + .as_source_pos(source_text_info) + .as_byte_index(start_pos); + let mut end = range + .end + .as_source_pos(source_text_info) + .as_byte_index(start_pos); + + let to_be_replaced = &source_text_info.text_str()[start..end]; + if to_be_replaced.starts_with('\'') + || to_be_replaced.starts_with('"') + || to_be_replaced.starts_with('`') + { + start += 1; + end -= 1; + } + + text_changes.push(TextChange { + new_text: new_specifier, + range: start..end, + }); + }; + + for desc in &module_info.dependencies { + match desc { + DependencyDescriptor::Static(desc) => { + if let Some(specifier) = + specifier_rewriter.rewrite(&desc.specifier, kind) + { + add_text_change(&mut text_changes, specifier, &desc.specifier_range); + } + } + DependencyDescriptor::Dynamic(desc) => match &desc.argument { + deno_graph::DynamicArgument::String(specifier) => { + if let Some(specifier) = specifier_rewriter.rewrite(&specifier, kind) + { + add_text_change(&mut text_changes, specifier, &desc.argument_range); + } + } + deno_graph::DynamicArgument::Template(_) => {} + deno_graph::DynamicArgument::Expr => {} + }, + } + } + + for ts_ref in &module_info.ts_references { + match ts_ref { + deno_graph::TypeScriptReference::Path(s) => { + if let Some(specifier) = + specifier_rewriter.rewrite(&s.text, RewriteKind::Declaration) + { + add_text_change(&mut text_changes, specifier, &s.range); + } + } + deno_graph::TypeScriptReference::Types(s) => { + match kind { + RewriteKind::Source => { + // Type reference comments in JS are a Deno specific concept, and + // are thus not relevant for the tarball. We remove them. + + let start_pos = source_text_info.range().start; + let start = s.range.start.as_source_pos(source_text_info); + let start = source_text_info.line_and_column_index(start); + + let line_start = source_text_info.line_start(start.line_index); + let line_end = source_text_info.line_end(start.line_index); + let line_text = source_text_info.line_text(start.line_index); + + let before = line_text[..start.column_index].to_string(); + + let index = before.rfind("///").expect("should have ///"); + let comment_start = line_start + index; + let comment_end = line_end; + + let range = comment_start.as_byte_index(start_pos) + ..comment_end.as_byte_index(start_pos); + + text_changes.push(TextChange { + new_text: "".to_string(), + range, + }); + } + RewriteKind::Declaration => { + if let Some(specifier) = + specifier_rewriter.rewrite(&s.text, RewriteKind::Declaration) + { + add_text_change(&mut text_changes, specifier, &s.range); + } + } + } + } + } + } + + for s in &module_info.jsdoc_imports { + if let Some(specifier) = + specifier_rewriter.rewrite(&s.text, RewriteKind::Declaration) + { + add_text_change(&mut text_changes, specifier, &s.range); + } + } + + apply_text_changes(source_text_info.text_str(), text_changes) +} + pub fn create_npm_dependencies<'a>( dependencies: impl Iterator>, ) -> Result, anyhow::Error> { @@ -366,32 +514,89 @@ pub fn create_npm_dependencies<'a>( Ok(npm_dependencies) } -pub fn create_npm_exports(exports: &ExportsMap) -> IndexMap { +pub fn create_npm_exports( + exports: &ExportsMap, + package_files: &IndexMap>, + source_rewrites: &HashMap<&ModuleSpecifier, ModuleSpecifier>, + declaration_rewrites: &HashMap<&ModuleSpecifier, ModuleSpecifier>, +) -> IndexMap { + let package_json_specifier = + ModuleSpecifier::parse("file:///package.json").unwrap(); + let mut npm_exports = IndexMap::new(); for (key, path) in exports.iter() { - // TODO: insert types exports here also - let import_path = - rewrite_specifier(path).unwrap_or_else(|| path.to_owned()); - npm_exports.insert(key.clone(), import_path); + let mut conditions = NpmExportConditions { + types: None, + default: None, + }; + + let specifier = ModuleSpecifier::parse(&format!( + "file:///{}", + path.trim_start_matches('.').trim_start_matches('/') + )) + .unwrap(); + + if let Some(source_specifier) = + follow_specifier(&specifier, source_rewrites) + { + if source_specifier.scheme() == "file" + && package_files.contains_key(source_specifier.path()) + { + let new_specifier = + relative_import_specifier(&package_json_specifier, source_specifier); + conditions.default = Some(new_specifier); + } + } + + if let Some(types_specifier) = + follow_specifier(&specifier, declaration_rewrites) + { + if types_specifier.scheme() == "file" + && package_files.contains_key(types_specifier.path()) + { + let new_specifier = + relative_import_specifier(&package_json_specifier, types_specifier); + if conditions.default.as_ref() != Some(&new_specifier) { + conditions.types = Some(new_specifier); + } + } + } + + npm_exports.insert(key.clone(), conditions); } npm_exports } -fn to_range( - parsed_source: &ParsedSource, - range: &PositionRange, -) -> std::ops::Range { - 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; +fn relative_import_specifier(base_specifier: &Url, specifier: &Url) -> String { + let relative = base_specifier.make_relative(&specifier).unwrap(); + if relative.is_empty() { + format!("./{}", specifier.path_segments().unwrap().last().unwrap()) + } else if relative.starts_with("../") { + relative.to_string() + } else { + format!("./{}", relative) } - if text.ends_with('"') || text.ends_with('\'') { - range.end -= 1; +} + +fn follow_specifier<'a>( + specifier: &'a Url, + remapped_specifiers: &'a HashMap<&Url, Url>, +) -> Option<&'a Url> { + let mut redirects = 0; + let mut types_specifier = specifier; + loop { + // avoid infinite loops + if redirects > 10 { + return None; + } + if let Some(rewritten) = remapped_specifiers.get(&types_specifier) { + types_specifier = rewritten; + } else { + break; + } + redirects += 1; } - range + Some(types_specifier) } #[cfg(test)] @@ -424,6 +629,7 @@ mod tests { use crate::ids::PackagePath; use crate::npm::tests::helpers; use crate::npm::tests::helpers::Spec; + use crate::npm::NPM_TARBALL_REVISION; use crate::tarball::exports_map_from_json; use super::create_npm_tarball; @@ -541,7 +747,7 @@ mod tests { scope: &scope, version: &version, graph: &graph, - sources: &module_analyzer.analyzer, + analyzer: &module_analyzer.analyzer, files: NpmTarballFiles::WithBytes(&files), dependencies: deps.iter(), }) @@ -573,6 +779,10 @@ mod tests { let mut output = String::new(); for (path, content) in transpiled_files { let content = String::from_utf8_lossy(&content); + let content = content.replace( + &format!("\"_jsr_revision\": {NPM_TARBALL_REVISION}"), + "\"_jsr_revision\": 0", + ); write!( &mut output, "== {path} ==\n{}\n{}", diff --git a/api/src/npm/types.rs b/api/src/npm/types.rs index 70a12b1a..3c218be2 100644 --- a/api/src/npm/types.rs +++ b/api/src/npm/types.rs @@ -64,6 +64,14 @@ pub struct NpmPackageInfo<'a> { pub time: IndexMap, } +#[derive(Debug, Serialize)] +pub struct NpmExportConditions { + #[serde(skip_serializing_if = "Option::is_none")] + pub types: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default: Option, +} + #[derive(Debug, Serialize)] pub struct NpmPackageJson<'a> { pub name: NpmMappedJsrPackageName<'a>, @@ -73,7 +81,7 @@ pub struct NpmPackageJson<'a> { #[serde(rename = "type")] pub module_type: String, pub dependencies: IndexMap, - pub exports: IndexMap, + pub exports: IndexMap, #[serde(rename = "_jsr_revision")] pub revision: u32, diff --git a/api/testdata/specs/npm_tarballs/additional_files.txt b/api/testdata/specs/npm_tarballs/additional_files.txt index 1c81ef6e..507e7a57 100644 --- a/api/testdata/specs/npm_tarballs/additional_files.txt +++ b/api/testdata/specs/npm_tarballs/additional_files.txt @@ -29,10 +29,14 @@ this is data == /foo.d.ts == export declare const foo: string; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLGNBQU0sS0FBSyxNQUFNLENBQVMifQ== == /foo.js == export const foo = 'bar'; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImh0dHA6Ly9qc3IudGVzdC9Ac2NvcGUvZm9vLzEuMC4wL2Zvby50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgY29uc3QgZm9vOiBzdHJpbmcgPSAnYmFyJztcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLE1BQU0sTUFBYyxNQUFNIn0= +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLE1BQU0sTUFBYyxNQUFNIn0= + +== /foo.ts == +export const foo: string = 'bar'; == /jsr.json == { @@ -52,9 +56,14 @@ export const foo = 'bar'; "type": "module", "dependencies": {}, "exports": { - "./foo": "./foo.js", - "./bar": "./bar.json" + "./foo": { + "types": "./foo.d.ts", + "default": "./foo.js" + }, + "./bar": { + "default": "./bar.json" + } }, - "_jsr_revision": 7 + "_jsr_revision": 0 } diff --git a/api/testdata/specs/npm_tarballs/import_jsr.txt b/api/testdata/specs/npm_tarballs/import_jsr.txt index 316f1ac3..d591c78e 100644 --- a/api/testdata/specs/npm_tarballs/import_jsr.txt +++ b/api/testdata/specs/npm_tarballs/import_jsr.txt @@ -1,8 +1,19 @@ # foo.js +/// import { html } from "jsr:@luca/flag@1"; await import("jsr:@luca/flag@1") # bar.mjs +/// +import { html } from "jsr:@luca/flag@1"; +await import("jsr:@luca/flag@1") + +# baz.ts +import { html } from "jsr:@luca/flag@1"; +html(); +await import("jsr:@luca/flag@1") + +# fizz.d.ts import { html } from "jsr:@luca/flag@1"; await import("jsr:@luca/flag@1") @@ -12,7 +23,9 @@ await import("jsr:@luca/flag@1") "version": "0.0.1", "exports": { "./foo": "./foo.js", - "./bar": "./bar.mjs" + "./bar": "./bar.mjs", + "./baz": "./baz.ts", + "./fizz": "./fizz.d.ts" } } @@ -40,10 +53,31 @@ await import("jsr:@luca/flag@1") # output == /bar.mjs == + +import { html } from "@jsr/luca__flag"; +await import("@jsr/luca__flag") + +== /baz.d.ts == + +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9 + +== /baz.js == +import { html } from "@jsr/luca__flag"; +html(); +await import("@jsr/luca__flag"); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhei50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxTQUFTLElBQUksMEJBQTJCO0FBQ3hDO0FBQ0EsTUFBTSxNQUFNLENBQUMifQ== + +== /baz.ts == +import { html } from "@jsr/luca__flag"; +html(); +await import("@jsr/luca__flag") + +== /fizz.d.ts == import { html } from "@jsr/luca__flag"; await import("@jsr/luca__flag") == /foo.js == + import { html } from "@jsr/luca__flag"; await import("@jsr/luca__flag") @@ -53,7 +87,9 @@ await import("@jsr/luca__flag") "version": "0.0.1", "exports": { "./foo": "./foo.js", - "./bar": "./bar.mjs" + "./bar": "./bar.mjs", + "./baz": "./baz.ts", + "./fizz": "./fizz.d.ts" } } @@ -65,9 +101,22 @@ await import("@jsr/luca__flag") "type": "module", "dependencies": {}, "exports": { - "./foo": "./foo.js", - "./bar": "./bar.mjs" + "./foo": { + "types": "./fizz.d.ts", + "default": "./foo.js" + }, + "./bar": { + "types": "./fizz.d.ts", + "default": "./bar.mjs" + }, + "./baz": { + "types": "./baz.d.ts", + "default": "./baz.js" + }, + "./fizz": { + "default": "./fizz.d.ts" + } }, - "_jsr_revision": 7 + "_jsr_revision": 0 } diff --git a/api/testdata/specs/npm_tarballs/import_npm.txt b/api/testdata/specs/npm_tarballs/import_npm.txt index ecec49aa..6c62390b 100644 --- a/api/testdata/specs/npm_tarballs/import_npm.txt +++ b/api/testdata/specs/npm_tarballs/import_npm.txt @@ -1,8 +1,19 @@ # foo.js +/// import { html } from "npm:lit@^2.2.7"; await import("npm:lit@^2.2.7") # bar.mjs +/// +import { html } from "npm:lit@^2.2.7"; +await import("npm:lit@^2.2.7") + +# baz.ts +import { html } from "npm:lit@^2.2.7"; +html(); +await import("npm:lit@^2.2.7") + +# fizz.d.ts import { html } from "npm:lit@^2.2.7"; await import("npm:lit@^2.2.7") @@ -12,7 +23,9 @@ await import("npm:lit@^2.2.7") "version": "0.0.1", "exports": { "./foo": "./foo.js", - "./bar": "./bar.mjs" + "./bar": "./bar.mjs", + "./baz": "./baz.ts", + "./fizz": "./fizz.d.ts" } } @@ -21,10 +34,31 @@ await import("npm:lit@^2.2.7") # output == /bar.mjs == + +import { html } from "lit"; +await import("lit") + +== /baz.d.ts == + +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9 + +== /baz.js == +import { html } from "lit"; +html(); +await import("lit"); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhei50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxTQUFTLElBQUksY0FBeUI7QUFDdEM7QUFDQSxNQUFNLE1BQU0sQ0FBQyJ9 + +== /baz.ts == +import { html } from "lit"; +html(); +await import("lit") + +== /fizz.d.ts == import { html } from "lit"; await import("lit") == /foo.js == + import { html } from "lit"; await import("lit") @@ -34,7 +68,9 @@ await import("lit") "version": "0.0.1", "exports": { "./foo": "./foo.js", - "./bar": "./bar.mjs" + "./bar": "./bar.mjs", + "./baz": "./baz.ts", + "./fizz": "./fizz.d.ts" } } @@ -46,9 +82,22 @@ await import("lit") "type": "module", "dependencies": {}, "exports": { - "./foo": "./foo.js", - "./bar": "./bar.mjs" + "./foo": { + "types": "./fizz.d.ts", + "default": "./foo.js" + }, + "./bar": { + "types": "./fizz.d.ts", + "default": "./bar.mjs" + }, + "./baz": { + "types": "./baz.d.ts", + "default": "./baz.js" + }, + "./fizz": { + "default": "./fizz.d.ts" + } }, - "_jsr_revision": 7 + "_jsr_revision": 0 } diff --git a/api/testdata/specs/npm_tarballs/js_with_dts.txt b/api/testdata/specs/npm_tarballs/js_with_dts.txt new file mode 100644 index 00000000..912fbcc8 --- /dev/null +++ b/api/testdata/specs/npm_tarballs/js_with_dts.txt @@ -0,0 +1,83 @@ +# main.js +/// + +export { A } from "./a.js"; +export const foo = 1; + +# main.d.ts +/// + +export { A } from "./a.js"; +export const foo: number; + +# a.js +/** other comment first /// */ /// + +export class A { + foo = 1; +} + +# a.d.ts +export class A { + foo: number; +} + +# jsr.json +{ + "name": "@scope/foo", + "version": "0.0.1", + "exports": { + ".": "./main.js" + } +} + +# output +== /a.d.ts == +export class A { + foo: number; +} + +== /a.js == +/** other comment first /// */ + +export class A { + foo = 1; +} + +== /jsr.json == +{ + "name": "@scope/foo", + "version": "0.0.1", + "exports": { + ".": "./main.js" + } +} + +== /main.d.ts == +/// + +export { A } from "./a.d.ts"; +export const foo: number; + +== /main.js == + + +export { A } from "./a.js"; +export const foo = 1; + +== /package.json == +{ + "name": "@jsr/scope__foo", + "version": "0.0.1", + "homepage": "http://jsr.test/@scope/foo", + "type": "module", + "dependencies": {}, + "exports": { + ".": { + "types": "./main.d.ts", + "default": "./main.js" + } + }, + "_jsr_revision": 0 +} + diff --git a/api/testdata/specs/npm_tarballs/jsdoc_import.txt b/api/testdata/specs/npm_tarballs/jsdoc_import.txt new file mode 100644 index 00000000..63b3b8b4 --- /dev/null +++ b/api/testdata/specs/npm_tarballs/jsdoc_import.txt @@ -0,0 +1,65 @@ +# index.js +/// +/** @type {import("./foo.ts").Num} */ +export const a = 1; + +# index.d.ts +export const a: import("./foo.ts").Num; + +# foo.ts +export type Num = number; + +# jsr.json +{ + "name": "@scope/foo", + "version": "0.0.1", + "exports": { + ".": "./index.js" + } +} + +# output +== /foo.d.ts == +export type Num = number; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZLE1BQU0sTUFBTSJ9 + +== /foo.js == + +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9 + +== /foo.ts == +export type Num = number; + +== /index.d.ts == +export const a: import("./foo.d.ts").Num; + +== /index.js == + +/** @type {import("./foo.d.ts").Num} */ +export const a = 1; + +== /jsr.json == +{ + "name": "@scope/foo", + "version": "0.0.1", + "exports": { + ".": "./index.js" + } +} + +== /package.json == +{ + "name": "@jsr/scope__foo", + "version": "0.0.1", + "homepage": "http://jsr.test/@scope/foo", + "type": "module", + "dependencies": {}, + "exports": { + ".": { + "types": "./index.d.ts", + "default": "./index.js" + } + }, + "_jsr_revision": 0 +} + diff --git a/api/testdata/specs/npm_tarballs/transpile.txt b/api/testdata/specs/npm_tarballs/transpile.txt index db10f57a..f1886209 100644 --- a/api/testdata/specs/npm_tarballs/transpile.txt +++ b/api/testdata/specs/npm_tarballs/transpile.txt @@ -32,6 +32,7 @@ export interface Foo { foo: string; } export declare function bar(): string; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxjQUFNLEtBQUssTUFBTSxDQUFTO0FBQ2pDLE9BQU8sY0FBTSxLQUFNLE1BQWU7QUFFbEMsaUJBQWlCO0VBQ2YsS0FBSyxNQUFNOztBQUdiLE9BQU8saUJBQVMsT0FBTyxNQUFNIn0= == /main.js == export const foo = 'foo'; @@ -39,7 +40,19 @@ export const bar = "bar"; export function bar() { return 'bar'; } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImh0dHA6Ly9qc3IudGVzdC9Ac2NvcGUvZm9vLzEuMC4wL21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGNvbnN0IGZvbzogc3RyaW5nID0gJ2Zvbyc7XG5leHBvcnQgY29uc3QgYmFyID0gXCJiYXJcIiBhcyBjb25zdDtcblxuZXhwb3J0IGludGVyZmFjZSBGb28ge1xuICBmb286IHN0cmluZztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGJhcigpOiBzdHJpbmcge1xuICByZXR1cm4gJ2Jhcic7XG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxNQUFNLE1BQWMsTUFBTTtBQUNqQyxPQUFPLE1BQU0sTUFBTSxNQUFlO0FBTWxDLE9BQU8sU0FBUztFQUNkLE9BQU87QUFDVCJ9 +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxNQUFNLE1BQWMsTUFBTTtBQUNqQyxPQUFPLE1BQU0sTUFBTSxNQUFlO0FBTWxDLE9BQU8sU0FBUztFQUNkLE9BQU87QUFDVCJ9 + +== /main.ts == +export const foo: string = 'foo'; +export const bar = "bar" as const; + +export interface Foo { + foo: string; +} + +export function bar(): string { + return 'bar'; +} == /package.json == { @@ -49,8 +62,11 @@ export function bar() { "type": "module", "dependencies": {}, "exports": { - ".": "./main.js" + ".": { + "types": "./main.d.ts", + "default": "./main.js" + } }, - "_jsr_revision": 7 + "_jsr_revision": 0 } diff --git a/api/testdata/specs/npm_tarballs/transpile_with_imports.txt b/api/testdata/specs/npm_tarballs/transpile_with_imports.txt new file mode 100644 index 00000000..fabfc86f --- /dev/null +++ b/api/testdata/specs/npm_tarballs/transpile_with_imports.txt @@ -0,0 +1,69 @@ +# foo.ts +export { add } from "./bar.ts"; + +# bar.ts +export function add(a: number, b: number): number { + return a + b; +} + +# jsr.json +{ + "name": "@scope/foo", + "version": "0.0.1", + "exports": { + ".": "./foo.ts" + } +} + +# output +== /bar.d.ts == +export declare function add(a: number, b: number): number; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLGlCQUFTLElBQUksR0FBRyxNQUFNLEVBQUUsR0FBRyxNQUFNLEdBQUcsTUFBTSJ9 + +== /bar.js == +export function add(a, b) { + return a + b; +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLFNBQVMsSUFBSSxDQUFTLEVBQUUsQ0FBUztFQUN0QyxPQUFPLElBQUk7QUFDYiJ9 + +== /bar.ts == +export function add(a: number, b: number): number { + return a + b; +} + +== /foo.d.ts == +export { add } from "./bar.d.ts"; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxTQUFTLEdBQUcscUJBQW1CIn0= + +== /foo.js == +export { add } from "./bar.js"; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxTQUFTLEdBQUcsbUJBQW1CIn0= + +== /foo.ts == +export { add } from "./bar.js"; + +== /jsr.json == +{ + "name": "@scope/foo", + "version": "0.0.1", + "exports": { + ".": "./foo.ts" + } +} + +== /package.json == +{ + "name": "@jsr/scope__foo", + "version": "0.0.1", + "homepage": "http://jsr.test/@scope/foo", + "type": "module", + "dependencies": {}, + "exports": { + ".": { + "types": "./foo.d.ts", + "default": "./foo.js" + } + }, + "_jsr_revision": 0 +} + From 1178acdd6a5402f13fd66c754a48b96075552be0 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 15 Apr 2024 12:35:43 +0200 Subject: [PATCH 2/8] lint --- api/src/npm/import_transform.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/npm/import_transform.rs b/api/src/npm/import_transform.rs index fa9eb9be..c51fecee 100644 --- a/api/src/npm/import_transform.rs +++ b/api/src/npm/import_transform.rs @@ -1,3 +1,4 @@ +// Copyright 2024 the JSR authors. All rights reserved. MIT license. use deno_ast::swc::ast::CallExpr; use deno_ast::swc::ast::Callee; use deno_ast::swc::ast::ExportAll; From 770ab476c931cf60528f786638524e80f6d930ec Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 15 Apr 2024 12:37:44 +0200 Subject: [PATCH 3/8] fix --- .env.example | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 03a09ea4..00000000 --- a/.env.example +++ /dev/null @@ -1,12 +0,0 @@ -DATABASE_URL=postgres://@localhost/registry -GITHUB_CLIENT_ID=xxx -GITHUB_CLIENT_SECRET=xxx -GCS_ENDPOINT=http://localhost:4080 -OTLP_ENDPOINT=http://localhost:4317 -MODULES_BUCKET=modules -PUBLISHING_BUCKET=publishing -DOCS_BUCKET=docs -NPM_BUCKET=npm -REGISTRY_URL=http://jsr.test -NPM_URL=http://npm.jsr.test - From 31020a36fdf5d8b49bc8dad662c8ecb3e306100e Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 15 Apr 2024 13:36:34 +0200 Subject: [PATCH 4/8] clippy --- api/src/npm/emit.rs | 8 +++---- api/src/npm/import_transform.rs | 8 +++---- api/src/npm/specifiers.rs | 6 ++--- api/src/npm/tarball.rs | 39 ++++----------------------------- 4 files changed, 15 insertions(+), 46 deletions(-) diff --git a/api/src/npm/emit.rs b/api/src/npm/emit.rs index fc765f63..30c17022 100644 --- a/api/src/npm/emit.rs +++ b/api/src/npm/emit.rs @@ -17,9 +17,9 @@ use crate::npm::import_transform::ImportRewriteTransformer; use super::specifiers::RewriteKind; use super::specifiers::SpecifierRewriter; -pub fn transpile_to_js<'a>( +pub fn transpile_to_js( source: &ParsedSource, - specifier_rewriter: SpecifierRewriter<'a>, + specifier_rewriter: SpecifierRewriter, ) -> Result { let emit_options = deno_ast::EmitOptions { source_map: SourceMapOption::Inline, @@ -68,10 +68,10 @@ pub fn transpile_to_js<'a>( Ok(emitted.text) } -pub fn transpile_to_dts<'a>( +pub fn transpile_to_dts( source: &ParsedSource, fast_check_module: &FastCheckTypeModule, - specifier_rewriter: SpecifierRewriter<'a>, + specifier_rewriter: SpecifierRewriter, ) -> Result { let dts = fast_check_module.dts.as_ref().unwrap(); diff --git a/api/src/npm/import_transform.rs b/api/src/npm/import_transform.rs index c51fecee..a595cbf5 100644 --- a/api/src/npm/import_transform.rs +++ b/api/src/npm/import_transform.rs @@ -31,7 +31,7 @@ impl<'a> VisitMut for ImportRewriteTransformer<'a> { if let Some(remapped) = self .specifier_rewriter - .rewrite(&node.src.value.as_str(), self.kind) + .rewrite(node.src.value.as_str(), self.kind) { node.src = Box::new(remapped.into()); } @@ -43,7 +43,7 @@ impl<'a> VisitMut for ImportRewriteTransformer<'a> { if let Some(src) = &node.src { if let Some(remapped) = self .specifier_rewriter - .rewrite(&src.value.as_str(), self.kind) + .rewrite(src.value.as_str(), self.kind) { node.src = Some(Box::new(remapped.into())); } @@ -55,7 +55,7 @@ impl<'a> VisitMut for ImportRewriteTransformer<'a> { if let Some(remapped) = self .specifier_rewriter - .rewrite(&node.src.value.as_str(), self.kind) + .rewrite(node.src.value.as_str(), self.kind) { node.src = Box::new(remapped.into()); } @@ -66,7 +66,7 @@ impl<'a> VisitMut for ImportRewriteTransformer<'a> { if let Some(remapped) = self .specifier_rewriter - .rewrite(&n.arg.value.as_str(), RewriteKind::Declaration) + .rewrite(n.arg.value.as_str(), RewriteKind::Declaration) { n.arg = remapped.into(); } diff --git a/api/src/npm/specifiers.rs b/api/src/npm/specifiers.rs index 2d6e1cf3..09a4b233 100644 --- a/api/src/npm/specifiers.rs +++ b/api/src/npm/specifiers.rs @@ -64,11 +64,11 @@ impl<'a> SpecifierRewriter<'a> { } } -fn relative_import_specifier( +pub fn relative_import_specifier( base_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier, ) -> String { - let relative = base_specifier.make_relative(&specifier).unwrap(); + let relative = base_specifier.make_relative(specifier).unwrap(); if relative.is_empty() { format!("./{}", specifier.path_segments().unwrap().last().unwrap()) } else if relative.starts_with("../") { @@ -78,7 +78,7 @@ fn relative_import_specifier( } } -fn follow_specifier<'a>( +pub fn follow_specifier<'a>( specifier: &'a ModuleSpecifier, remapped_specifiers: &'a HashMap<&ModuleSpecifier, ModuleSpecifier>, ) -> Option<&'a ModuleSpecifier> { diff --git a/api/src/npm/tarball.rs b/api/src/npm/tarball.rs index ee612a09..9eec44ba 100644 --- a/api/src/npm/tarball.rs +++ b/api/src/npm/tarball.rs @@ -36,6 +36,8 @@ use crate::ids::Version; use super::emit::transpile_to_dts; use super::emit::transpile_to_js; +use super::specifiers::follow_specifier; +use super::specifiers::relative_import_specifier; use super::specifiers::rewrite_file_specifier_extension; use super::specifiers::Extension; use super::specifiers::RewriteKind; @@ -370,7 +372,7 @@ pub async fn create_npm_tarball<'a>( }) } -fn rewrite_specifiers<'a>( +fn rewrite_specifiers( source_text_info: &SourceTextInfo, module_info: &ModuleInfo, specifier_rewriter: SpecifierRewriter, @@ -417,8 +419,7 @@ fn rewrite_specifiers<'a>( } DependencyDescriptor::Dynamic(desc) => match &desc.argument { deno_graph::DynamicArgument::String(specifier) => { - if let Some(specifier) = specifier_rewriter.rewrite(&specifier, kind) - { + if let Some(specifier) = specifier_rewriter.rewrite(specifier, kind) { add_text_change(&mut text_changes, specifier, &desc.argument_range); } } @@ -567,38 +568,6 @@ pub fn create_npm_exports( npm_exports } -fn relative_import_specifier(base_specifier: &Url, specifier: &Url) -> String { - let relative = base_specifier.make_relative(&specifier).unwrap(); - if relative.is_empty() { - format!("./{}", specifier.path_segments().unwrap().last().unwrap()) - } else if relative.starts_with("../") { - relative.to_string() - } else { - format!("./{}", relative) - } -} - -fn follow_specifier<'a>( - specifier: &'a Url, - remapped_specifiers: &'a HashMap<&Url, Url>, -) -> Option<&'a Url> { - let mut redirects = 0; - let mut types_specifier = specifier; - loop { - // avoid infinite loops - if redirects > 10 { - return None; - } - if let Some(rewritten) = remapped_specifiers.get(&types_specifier) { - types_specifier = rewritten; - } else { - break; - } - redirects += 1; - } - Some(types_specifier) -} - #[cfg(test)] mod tests { use std::collections::HashMap; From 9f95ca092041f374834cef609fab8c3db7026a93 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 15 Apr 2024 14:22:43 +0200 Subject: [PATCH 5/8] fmt --- api/src/npm/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/npm/mod.rs b/api/src/npm/mod.rs index 92a37015..2a490acd 100644 --- a/api/src/npm/mod.rs +++ b/api/src/npm/mod.rs @@ -1,11 +1,11 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. +mod emit; +mod import_transform; mod specifiers; mod tarball; #[cfg(test)] mod tests; mod types; -mod import_transform; -mod emit; use chrono::SecondsFormat; use deno_semver::package::PackageReq; From ad8289b2556fe8223b45ecd53663ad0055f02dfd Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 15 Apr 2024 15:13:45 +0200 Subject: [PATCH 6/8] add unit test for relative_import_specifier --- api/src/npm/specifiers.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/api/src/npm/specifiers.rs b/api/src/npm/specifiers.rs index 09a4b233..ee72c047 100644 --- a/api/src/npm/specifiers.rs +++ b/api/src/npm/specifiers.rs @@ -271,4 +271,36 @@ mod tests { Some("foo/bar.d.ts".to_owned()) ); } + + #[test] + fn test_relative_import_specifier() { + assert_eq!( + relative_import_specifier( + &ModuleSpecifier::parse("file:///a/b/c.ts").unwrap(), + &ModuleSpecifier::parse("file:///a/b/d.ts").unwrap(), + ), + "./d.ts", + ); + assert_eq!( + relative_import_specifier( + &ModuleSpecifier::parse("file:///a/b/c.ts").unwrap(), + &ModuleSpecifier::parse("file:///a/d.ts").unwrap(), + ), + "../d.ts", + ); + assert_eq!( + relative_import_specifier( + &ModuleSpecifier::parse("file:///a/b/c.ts").unwrap(), + &ModuleSpecifier::parse("file:///a/b/c.ts").unwrap(), + ), + "./c.ts", + ); + assert_eq!( + relative_import_specifier( + &ModuleSpecifier::parse("file:///a/b/c.ts").unwrap(), + &ModuleSpecifier::parse("file:///a/b/c/d.ts").unwrap(), + ), + "./c/d.ts", + ); + } } From 258081fd7a6840fa38ce66db3ae22b931e719f4e Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 16 Apr 2024 18:09:21 +0200 Subject: [PATCH 7/8] fixes --- api/src/npm/import_transform.rs | 4 ---- api/src/npm/tarball.rs | 18 ++++++------------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/api/src/npm/import_transform.rs b/api/src/npm/import_transform.rs index a595cbf5..646d45df 100644 --- a/api/src/npm/import_transform.rs +++ b/api/src/npm/import_transform.rs @@ -22,10 +22,6 @@ pub struct ImportRewriteTransformer<'a> { } impl<'a> VisitMut for ImportRewriteTransformer<'a> { - fn visit_mut_module(&mut self, module: &mut Module) { - module.visit_mut_children_with(self); - } - fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) { node.visit_mut_children_with(self); diff --git a/api/src/npm/tarball.rs b/api/src/npm/tarball.rs index 9eec44ba..7f4b8715 100644 --- a/api/src/npm/tarball.rs +++ b/api/src/npm/tarball.rs @@ -195,10 +195,8 @@ pub async fn create_npm_tarball<'a>( specifier_rewriter, RewriteKind::Source, ); - package_files.insert( - js.specifier.path().to_owned(), - rewritten.as_bytes().to_vec(), - ); + package_files + .insert(js.specifier.path().to_owned(), rewritten.into_bytes()); } deno_ast::MediaType::Dts | deno_ast::MediaType::Dmts => { let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); @@ -211,10 +209,8 @@ pub async fn create_npm_tarball<'a>( specifier_rewriter, RewriteKind::Declaration, ); - package_files.insert( - js.specifier.path().to_owned(), - rewritten.as_bytes().to_vec(), - ); + package_files + .insert(js.specifier.path().to_owned(), rewritten.into_bytes()); } deno_ast::MediaType::Jsx => { let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); @@ -235,10 +231,8 @@ pub async fn create_npm_tarball<'a>( specifier_rewriter, RewriteKind::Source, ); - package_files.insert( - js.specifier.path().to_owned(), - rewritten.as_bytes().to_vec(), - ); + package_files + .insert(js.specifier.path().to_owned(), rewritten.into_bytes()); let parsed_source = sources.get_parsed_source(&js.specifier).unwrap(); let source = From 13415af33117a42a706e3adcb35a37ef88b553f1 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 16 Apr 2024 18:23:06 +0200 Subject: [PATCH 8/8] lint --- api/src/npm/import_transform.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/npm/import_transform.rs b/api/src/npm/import_transform.rs index 646d45df..64f65c5a 100644 --- a/api/src/npm/import_transform.rs +++ b/api/src/npm/import_transform.rs @@ -6,7 +6,6 @@ use deno_ast::swc::ast::Expr; use deno_ast::swc::ast::ExprOrSpread; use deno_ast::swc::ast::ImportDecl; use deno_ast::swc::ast::Lit; -use deno_ast::swc::ast::Module; use deno_ast::swc::ast::NamedExport; use deno_ast::swc::ast::Str; use deno_ast::swc::ast::TsImportType;