2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2021-10-10 17:26:22 -04:00
2024-06-05 11:04:16 -04:00
use async_trait ::async_trait ;
2024-05-13 12:55:31 -04:00
use dashmap ::DashMap ;
2024-05-18 11:42:03 -04:00
use dashmap ::DashSet ;
2023-12-06 19:03:18 -05:00
use deno_ast ::MediaType ;
2024-07-03 20:54:33 -04:00
use deno_config ::workspace ::MappedResolution ;
use deno_config ::workspace ::MappedResolutionError ;
use deno_config ::workspace ::WorkspaceResolver ;
2023-03-03 17:27:05 -05:00
use deno_core ::anyhow ::anyhow ;
2024-01-27 12:10:36 -05:00
use deno_core ::anyhow ::Context ;
2023-01-24 08:23:19 -05:00
use deno_core ::error ::AnyError ;
2024-06-05 11:04:16 -04:00
use deno_core ::ModuleSourceCode ;
2021-10-10 17:26:22 -04:00
use deno_core ::ModuleSpecifier ;
2023-10-24 09:37:02 -04:00
use deno_graph ::source ::ResolutionMode ;
2023-10-20 00:02:08 -04:00
use deno_graph ::source ::ResolveError ;
2021-10-10 17:26:22 -04:00
use deno_graph ::source ::Resolver ;
2023-02-22 14:15:25 -05:00
use deno_graph ::source ::UnknownBuiltInNodeModuleError ;
2022-11-02 10:47:02 -04:00
use deno_graph ::source ::DEFAULT_JSX_IMPORT_SOURCE_MODULE ;
2024-06-11 08:55:12 -04:00
use deno_graph ::NpmLoadError ;
use deno_graph ::NpmResolvePkgReqsResult ;
use deno_npm ::resolution ::NpmResolutionError ;
2024-07-23 17:34:46 -04:00
use deno_package_json ::PackageJsonDepValue ;
2024-01-27 12:10:36 -05:00
use deno_runtime ::deno_fs ;
2023-10-25 14:39:00 -04:00
use deno_runtime ::deno_fs ::FileSystem ;
2024-07-09 12:15:03 -04:00
use deno_runtime ::deno_node ::errors ::ClosestPkgJsonError ;
2024-07-16 18:32:41 -04:00
use deno_runtime ::deno_node ::errors ::NodeResolveError ;
use deno_runtime ::deno_node ::errors ::ResolvePkgSubpathFromDenoModuleError ;
2024-07-09 12:15:03 -04:00
use deno_runtime ::deno_node ::errors ::UrlToNodeResolutionError ;
2023-02-22 14:15:25 -05:00
use deno_runtime ::deno_node ::is_builtin_node_module ;
2023-10-26 21:22:15 -04:00
use deno_runtime ::deno_node ::parse_npm_pkg_name ;
2024-07-10 14:46:25 -04:00
use deno_runtime ::deno_node ::NodeModuleKind ;
2023-10-25 14:39:00 -04:00
use deno_runtime ::deno_node ::NodeResolution ;
use deno_runtime ::deno_node ::NodeResolutionMode ;
use deno_runtime ::deno_node ::NodeResolver ;
2023-10-26 21:22:15 -04:00
use deno_runtime ::deno_node ::NpmResolver as DenoNodeNpmResolver ;
2024-03-05 19:23:51 -05:00
use deno_runtime ::deno_node ::PackageJson ;
2024-04-17 10:19:55 -04:00
use deno_runtime ::fs_util ::specifier_to_file_path ;
2023-10-25 14:39:00 -04:00
use deno_semver ::npm ::NpmPackageReqReference ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageReq ;
2023-12-06 19:03:18 -05:00
use std ::borrow ::Cow ;
use std ::path ::Path ;
2023-08-17 12:14:22 -04:00
use std ::path ::PathBuf ;
2021-11-08 20:26:39 -05:00
use std ::sync ::Arc ;
2021-10-10 17:26:22 -04:00
2022-11-25 17:00:28 -05:00
use crate ::args ::JsxImportSourceConfig ;
2024-03-07 08:59:57 -05:00
use crate ::args ::DENO_DISABLE_PEDANTIC_NODE_WARNINGS ;
2024-02-13 11:22:30 -05:00
use crate ::colors ;
2024-01-27 12:10:36 -05:00
use crate ::node ::CliNodeCodeTranslator ;
2023-10-26 21:22:15 -04:00
use crate ::npm ::ByonmCliNpmResolver ;
2023-09-30 12:06:38 -04:00
use crate ::npm ::CliNpmResolver ;
2023-10-03 19:05:06 -04:00
use crate ::npm ::InnerCliNpmResolverRef ;
2023-04-11 18:10:51 -04:00
use crate ::util ::sync ::AtomicFlag ;
2022-08-24 13:36:05 -04:00
2024-02-13 11:22:30 -05:00
pub fn format_range_with_colors ( range : & deno_graph ::Range ) -> String {
format! (
" {}:{}:{} " ,
colors ::cyan ( range . specifier . as_str ( ) ) ,
colors ::yellow ( & ( range . start . line + 1 ) . to_string ( ) ) ,
colors ::yellow ( & ( range . start . character + 1 ) . to_string ( ) )
)
}
2024-01-27 12:10:36 -05:00
pub struct ModuleCodeStringSource {
2024-06-05 11:04:16 -04:00
pub code : ModuleSourceCode ,
2024-01-27 12:10:36 -05:00
pub found_url : ModuleSpecifier ,
pub media_type : MediaType ,
}
2024-03-05 19:23:51 -05:00
#[ derive(Debug) ]
2024-01-27 12:10:36 -05:00
pub struct CliNodeResolver {
2024-07-10 14:46:25 -04:00
cjs_resolutions : Arc < CjsResolutionStore > ,
2024-03-05 19:23:51 -05:00
fs : Arc < dyn deno_fs ::FileSystem > ,
2024-01-27 12:10:36 -05:00
node_resolver : Arc < NodeResolver > ,
2024-03-05 19:23:51 -05:00
// todo(dsherret): remove this pub(crate)
2024-01-27 12:10:36 -05:00
pub ( crate ) npm_resolver : Arc < dyn CliNpmResolver > ,
}
impl CliNodeResolver {
pub fn new (
2024-07-10 14:46:25 -04:00
cjs_resolutions : Arc < CjsResolutionStore > ,
2024-03-05 19:23:51 -05:00
fs : Arc < dyn deno_fs ::FileSystem > ,
2024-01-27 12:10:36 -05:00
node_resolver : Arc < NodeResolver > ,
npm_resolver : Arc < dyn CliNpmResolver > ,
) -> Self {
Self {
cjs_resolutions ,
2024-03-05 19:23:51 -05:00
fs ,
2024-01-27 12:10:36 -05:00
node_resolver ,
npm_resolver ,
}
}
2024-05-16 03:09:35 -04:00
pub fn in_npm_package ( & self , specifier : & ModuleSpecifier ) -> bool {
self . npm_resolver . in_npm_package ( specifier )
2024-01-27 12:10:36 -05:00
}
2024-03-05 19:23:51 -05:00
pub fn get_closest_package_json (
& self ,
referrer : & ModuleSpecifier ,
2024-07-09 12:15:03 -04:00
) -> Result < Option < Arc < PackageJson > > , ClosestPkgJsonError > {
2024-06-26 17:24:10 -04:00
self . node_resolver . get_closest_package_json ( referrer )
2024-03-05 19:23:51 -05:00
}
2024-01-27 12:10:36 -05:00
pub fn resolve_if_in_npm_package (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
2024-03-05 19:23:51 -05:00
mode : NodeResolutionMode ,
2024-07-16 18:32:41 -04:00
) -> Option < Result < Option < NodeResolution > , NodeResolveError > > {
2024-01-27 12:10:36 -05:00
if self . in_npm_package ( referrer ) {
// we're in an npm package, so use node resolution
2024-06-06 23:37:53 -04:00
Some ( self . resolve ( specifier , referrer , mode ) )
2024-01-27 12:10:36 -05:00
} else {
None
}
}
2024-03-05 19:23:51 -05:00
pub fn resolve (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
mode : NodeResolutionMode ,
2024-07-16 18:32:41 -04:00
) -> Result < Option < NodeResolution > , NodeResolveError > {
2024-07-10 14:46:25 -04:00
let referrer_kind = if self . cjs_resolutions . contains ( referrer ) {
NodeModuleKind ::Cjs
} else {
NodeModuleKind ::Esm
} ;
2024-07-16 18:32:41 -04:00
let maybe_res =
2024-07-09 12:15:03 -04:00
self
. node_resolver
2024-07-16 18:32:41 -04:00
. resolve ( specifier , referrer , referrer_kind , mode ) ? ;
Ok ( self . handle_node_resolution ( maybe_res ) )
2024-03-05 19:23:51 -05:00
}
2024-01-27 12:10:36 -05:00
pub fn resolve_req_reference (
& self ,
req_ref : & NpmPackageReqReference ,
referrer : & ModuleSpecifier ,
2024-03-05 19:23:51 -05:00
mode : NodeResolutionMode ,
) -> Result < NodeResolution , AnyError > {
2024-07-03 20:54:33 -04:00
self . resolve_req_with_sub_path (
req_ref . req ( ) ,
2024-03-05 19:23:51 -05:00
req_ref . sub_path ( ) ,
referrer ,
mode ,
2024-07-03 20:54:33 -04:00
)
}
pub fn resolve_req_with_sub_path (
& self ,
req : & PackageReq ,
sub_path : Option < & str > ,
referrer : & ModuleSpecifier ,
mode : NodeResolutionMode ,
) -> Result < NodeResolution , AnyError > {
let package_folder = self
. npm_resolver
. resolve_pkg_folder_from_deno_module_req ( req , referrer ) ? ;
let maybe_resolution = self
. maybe_resolve_package_sub_path_from_deno_module (
& package_folder ,
sub_path ,
2024-07-09 12:15:03 -04:00
Some ( referrer ) ,
2024-07-03 20:54:33 -04:00
mode ,
) ? ;
2024-03-05 19:23:51 -05:00
match maybe_resolution {
Some ( resolution ) = > Ok ( resolution ) ,
None = > {
if self . npm_resolver . as_byonm ( ) . is_some ( ) {
let package_json_path = package_folder . join ( " package.json " ) ;
if ! self . fs . exists_sync ( & package_json_path ) {
return Err ( anyhow! (
" Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `npm install`? " ,
package_json_path . display ( )
) ) ;
}
}
Err ( anyhow! (
2024-07-03 20:54:33 -04:00
" Failed resolving '{}{}' in '{}'. " ,
req ,
sub_path . map ( | s | format! ( " / {} " , s ) ) . unwrap_or_default ( ) ,
2024-03-05 19:23:51 -05:00
package_folder . display ( )
) )
}
}
2024-01-27 12:10:36 -05:00
}
2024-03-05 19:23:51 -05:00
pub fn resolve_package_sub_path_from_deno_module (
2024-01-27 12:10:36 -05:00
& self ,
package_folder : & Path ,
sub_path : Option < & str > ,
2024-07-09 12:15:03 -04:00
maybe_referrer : Option < & ModuleSpecifier > ,
2024-03-05 19:23:51 -05:00
mode : NodeResolutionMode ,
2024-07-03 20:54:33 -04:00
) -> Result < NodeResolution , AnyError > {
self
. maybe_resolve_package_sub_path_from_deno_module (
package_folder ,
sub_path ,
2024-07-09 12:15:03 -04:00
maybe_referrer ,
2024-07-03 20:54:33 -04:00
mode ,
) ?
. ok_or_else ( | | {
anyhow! (
" Failed resolving '{}' in '{}'. " ,
sub_path
. map ( | s | format! ( " / {} " , s ) )
. unwrap_or_else ( | | " . " . to_string ( ) ) ,
package_folder . display ( ) ,
)
} )
}
pub fn maybe_resolve_package_sub_path_from_deno_module (
& self ,
package_folder : & Path ,
sub_path : Option < & str > ,
2024-07-09 12:15:03 -04:00
maybe_referrer : Option < & ModuleSpecifier > ,
2024-07-03 20:54:33 -04:00
mode : NodeResolutionMode ,
2024-07-16 18:32:41 -04:00
) -> Result < Option < NodeResolution > , ResolvePkgSubpathFromDenoModuleError > {
let maybe_res = self
. node_resolver
. resolve_package_subpath_from_deno_module (
package_folder ,
sub_path ,
maybe_referrer ,
mode ,
) ? ;
Ok ( self . handle_node_resolution ( maybe_res ) )
2024-01-27 12:10:36 -05:00
}
2024-03-05 19:23:51 -05:00
pub fn handle_if_in_node_modules (
& self ,
specifier : ModuleSpecifier ,
) -> Result < ModuleSpecifier , AnyError > {
// skip canonicalizing if we definitely know it's unnecessary
if specifier . scheme ( ) = = " file "
& & specifier . path ( ) . contains ( " /node_modules/ " )
{
// Specifiers in the node_modules directory are canonicalized
// so canoncalize then check if it's in the node_modules directory.
// If so, check if we need to store this specifier as being a CJS
// resolution.
let specifier =
crate ::node ::resolve_specifier_into_node_modules ( & specifier ) ;
if self . in_npm_package ( & specifier ) {
2024-07-10 14:46:25 -04:00
let resolution =
self . node_resolver . url_to_node_resolution ( specifier ) ? ;
if let NodeResolution ::CommonJs ( specifier ) = & resolution {
self . cjs_resolutions . insert ( specifier . clone ( ) ) ;
2024-03-05 19:23:51 -05:00
}
2024-07-10 14:46:25 -04:00
return Ok ( resolution . into_url ( ) ) ;
2024-03-05 19:23:51 -05:00
}
}
Ok ( specifier )
}
2024-04-15 17:50:52 -04:00
pub fn url_to_node_resolution (
& self ,
specifier : ModuleSpecifier ,
2024-07-09 12:15:03 -04:00
) -> Result < NodeResolution , UrlToNodeResolutionError > {
2024-04-15 17:50:52 -04:00
self . node_resolver . url_to_node_resolution ( specifier )
}
2024-07-16 18:32:41 -04:00
fn handle_node_resolution (
2024-01-27 12:10:36 -05:00
& self ,
2024-07-16 18:32:41 -04:00
maybe_resolution : Option < NodeResolution > ,
) -> Option < NodeResolution > {
if let Some ( NodeResolution ::CommonJs ( specifier ) ) = & maybe_resolution {
// remember that this was a common js resolution
self . cjs_resolutions . insert ( specifier . clone ( ) ) ;
2024-01-27 12:10:36 -05:00
}
2024-07-16 18:32:41 -04:00
maybe_resolution
2024-01-27 12:10:36 -05:00
}
}
2024-05-16 03:09:35 -04:00
#[ derive(Clone) ]
2024-01-27 12:10:36 -05:00
pub struct NpmModuleLoader {
cjs_resolutions : Arc < CjsResolutionStore > ,
node_code_translator : Arc < CliNodeCodeTranslator > ,
fs : Arc < dyn deno_fs ::FileSystem > ,
node_resolver : Arc < CliNodeResolver > ,
}
impl NpmModuleLoader {
pub fn new (
cjs_resolutions : Arc < CjsResolutionStore > ,
node_code_translator : Arc < CliNodeCodeTranslator > ,
fs : Arc < dyn deno_fs ::FileSystem > ,
node_resolver : Arc < CliNodeResolver > ,
) -> Self {
Self {
cjs_resolutions ,
node_code_translator ,
fs ,
node_resolver ,
}
}
2024-05-18 11:42:03 -04:00
pub async fn load_if_in_npm_package (
2024-01-27 12:10:36 -05:00
& self ,
specifier : & ModuleSpecifier ,
maybe_referrer : Option < & ModuleSpecifier > ,
) -> Option < Result < ModuleCodeStringSource , AnyError > > {
if self . node_resolver . in_npm_package ( specifier ) {
2024-06-06 23:37:53 -04:00
Some ( self . load ( specifier , maybe_referrer ) . await )
2024-01-27 12:10:36 -05:00
} else {
None
}
}
2024-05-18 11:42:03 -04:00
pub async fn load (
2024-01-27 12:10:36 -05:00
& self ,
specifier : & ModuleSpecifier ,
maybe_referrer : Option < & ModuleSpecifier > ,
) -> Result < ModuleCodeStringSource , AnyError > {
let file_path = specifier . to_file_path ( ) . unwrap ( ) ;
let code = self
. fs
2024-06-05 11:04:16 -04:00
. read_file_async ( file_path . clone ( ) , None )
2024-05-18 11:42:03 -04:00
. await
2024-01-27 12:10:36 -05:00
. map_err ( AnyError ::from )
. with_context ( | | {
if file_path . is_dir ( ) {
// directory imports are not allowed when importing from an
// ES module, so provide the user with a helpful error message
let dir_path = file_path ;
let mut msg = " Directory import " . to_string ( ) ;
msg . push_str ( & dir_path . to_string_lossy ( ) ) ;
if let Some ( referrer ) = & maybe_referrer {
msg . push_str ( " is not supported resolving import from " ) ;
msg . push_str ( referrer . as_str ( ) ) ;
let entrypoint_name = [ " index.mjs " , " index.js " , " index.cjs " ]
. iter ( )
. find ( | e | dir_path . join ( e ) . is_file ( ) ) ;
if let Some ( entrypoint_name ) = entrypoint_name {
msg . push_str ( " \n Did you mean to import " ) ;
msg . push_str ( entrypoint_name ) ;
msg . push_str ( " within the directory? " ) ;
}
}
msg
} else {
let mut msg = " Unable to load " . to_string ( ) ;
msg . push_str ( & file_path . to_string_lossy ( ) ) ;
if let Some ( referrer ) = & maybe_referrer {
msg . push_str ( " imported from " ) ;
msg . push_str ( referrer . as_str ( ) ) ;
}
msg
}
} ) ? ;
let code = if self . cjs_resolutions . contains ( specifier ) {
// translate cjs to esm if it's cjs and inject node globals
2024-06-08 12:36:13 -04:00
let code = match String ::from_utf8_lossy ( & code ) {
Cow ::Owned ( code ) = > code ,
// SAFETY: `String::from_utf8_lossy` guarantees that the result is valid
// UTF-8 if `Cow::Borrowed` is returned.
Cow ::Borrowed ( _ ) = > unsafe { String ::from_utf8_unchecked ( code ) } ,
} ;
2024-06-05 11:04:16 -04:00
ModuleSourceCode ::String (
self
. node_code_translator
2024-06-06 23:37:53 -04:00
. translate_cjs_to_esm ( specifier , Some ( code ) )
2024-06-05 11:04:16 -04:00
. await ?
. into ( ) ,
)
2024-01-27 12:10:36 -05:00
} else {
// esm and json code is untouched
2024-06-05 11:04:16 -04:00
ModuleSourceCode ::Bytes ( code . into_boxed_slice ( ) . into ( ) )
2024-01-27 12:10:36 -05:00
} ;
Ok ( ModuleCodeStringSource {
2024-06-05 11:04:16 -04:00
code ,
2024-01-27 12:10:36 -05:00
found_url : specifier . clone ( ) ,
media_type : MediaType ::from_specifier ( specifier ) ,
} )
}
}
/// Keeps track of what module specifiers were resolved as CJS.
#[ derive(Debug, Default) ]
2024-05-18 11:42:03 -04:00
pub struct CjsResolutionStore ( DashSet < ModuleSpecifier > ) ;
2024-01-27 12:10:36 -05:00
impl CjsResolutionStore {
pub fn contains ( & self , specifier : & ModuleSpecifier ) -> bool {
2024-05-18 11:42:03 -04:00
self . 0. contains ( specifier )
2024-01-27 12:10:36 -05:00
}
pub fn insert ( & self , specifier : ModuleSpecifier ) {
2024-05-18 11:42:03 -04:00
self . 0. insert ( specifier ) ;
2024-01-27 12:10:36 -05:00
}
}
2022-11-02 10:47:02 -04:00
/// A resolver that takes care of resolution, taking into account loaded
/// import map, JSX settings.
2023-04-14 16:22:33 -04:00
#[ derive(Debug) ]
2023-02-15 11:30:54 -05:00
pub struct CliGraphResolver {
2024-07-03 20:54:33 -04:00
node_resolver : Option < Arc < CliNodeResolver > > ,
npm_resolver : Option < Arc < dyn CliNpmResolver > > ,
2023-12-07 15:59:13 -05:00
sloppy_imports_resolver : Option < SloppyImportsResolver > ,
2024-07-03 20:54:33 -04:00
workspace_resolver : Arc < WorkspaceResolver > ,
2022-11-02 10:47:02 -04:00
maybe_default_jsx_import_source : Option < String > ,
2024-04-30 14:12:35 -04:00
maybe_default_jsx_import_source_types : Option < String > ,
2022-11-02 10:47:02 -04:00
maybe_jsx_import_source_module : Option < String > ,
2023-08-17 12:14:22 -04:00
maybe_vendor_specifier : Option < ModuleSpecifier > ,
2024-06-11 08:55:12 -04:00
found_package_json_dep_flag : AtomicFlag ,
2023-10-20 00:02:08 -04:00
bare_node_builtins_enabled : bool ,
2023-02-22 14:15:25 -05:00
}
2023-08-17 12:14:22 -04:00
pub struct CliGraphResolverOptions < ' a > {
2024-03-05 19:23:51 -05:00
pub node_resolver : Option < Arc < CliNodeResolver > > ,
2023-10-25 14:39:00 -04:00
pub npm_resolver : Option < Arc < dyn CliNpmResolver > > ,
2024-07-03 20:54:33 -04:00
pub sloppy_imports_resolver : Option < SloppyImportsResolver > ,
pub workspace_resolver : Arc < WorkspaceResolver > ,
pub bare_node_builtins_enabled : bool ,
2023-08-17 12:14:22 -04:00
pub maybe_jsx_import_source_config : Option < JsxImportSourceConfig > ,
pub maybe_vendor_dir : Option < & ' a PathBuf > ,
}
2023-02-15 11:30:54 -05:00
impl CliGraphResolver {
2023-10-25 14:39:00 -04:00
pub fn new ( options : CliGraphResolverOptions ) -> Self {
2023-02-15 11:30:54 -05:00
Self {
2024-07-03 20:54:33 -04:00
node_resolver : options . node_resolver ,
npm_resolver : options . npm_resolver ,
2023-12-06 19:03:18 -05:00
sloppy_imports_resolver : options . sloppy_imports_resolver ,
2024-07-03 20:54:33 -04:00
workspace_resolver : options . workspace_resolver ,
2023-08-17 12:14:22 -04:00
maybe_default_jsx_import_source : options
. maybe_jsx_import_source_config
2023-02-15 11:30:54 -05:00
. as_ref ( )
. and_then ( | c | c . default_specifier . clone ( ) ) ,
2024-04-30 14:12:35 -04:00
maybe_default_jsx_import_source_types : options
. maybe_jsx_import_source_config
. as_ref ( )
. and_then ( | c | c . default_types_specifier . clone ( ) ) ,
2023-08-17 12:14:22 -04:00
maybe_jsx_import_source_module : options
. maybe_jsx_import_source_config
2023-02-15 11:30:54 -05:00
. map ( | c | c . module ) ,
2023-08-17 12:14:22 -04:00
maybe_vendor_specifier : options
. maybe_vendor_dir
. and_then ( | v | ModuleSpecifier ::from_directory_path ( v ) . ok ( ) ) ,
2023-04-11 18:10:51 -04:00
found_package_json_dep_flag : Default ::default ( ) ,
2023-10-20 00:02:08 -04:00
bare_node_builtins_enabled : options . bare_node_builtins_enabled ,
2022-01-31 17:33:57 -05:00
}
2021-10-10 17:26:22 -04:00
}
2021-11-08 20:26:39 -05:00
2022-11-02 10:47:02 -04:00
pub fn as_graph_resolver ( & self ) -> & dyn Resolver {
2021-11-08 20:26:39 -05:00
self
}
2023-02-22 14:15:25 -05:00
2024-06-05 11:04:16 -04:00
pub fn create_graph_npm_resolver ( & self ) -> WorkerCliNpmGraphResolver {
WorkerCliNpmGraphResolver {
npm_resolver : self . npm_resolver . as_ref ( ) ,
2024-06-11 08:55:12 -04:00
found_package_json_dep_flag : & self . found_package_json_dep_flag ,
2024-06-05 11:04:16 -04:00
bare_node_builtins_enabled : self . bare_node_builtins_enabled ,
}
2023-02-22 14:15:25 -05:00
}
2023-04-11 18:10:51 -04:00
2024-07-16 18:32:41 -04:00
// todo(dsherret): update this and the surrounding code to handle the structured errors from NodeResolver
2023-10-26 21:22:15 -04:00
fn check_surface_byonm_node_error (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
original_err : AnyError ,
resolver : & ByonmCliNpmResolver ,
) -> Result < ( ) , AnyError > {
if let Ok ( ( pkg_name , _ , _ ) ) = parse_npm_pkg_name ( specifier , referrer ) {
2024-06-08 20:05:28 -04:00
match resolver . resolve_package_folder_from_package ( & pkg_name , referrer ) {
2023-10-26 21:22:15 -04:00
Ok ( _ ) = > {
return Err ( original_err ) ;
}
Err ( _ ) = > {
if resolver
. find_ancestor_package_json_with_dep ( & pkg_name , referrer )
. is_some ( )
{
return Err ( anyhow! (
concat! (
" Could not resolve \" {} \" , but found it in a package.json. " ,
" Deno expects the node_modules/ directory to be up to date. " ,
" Did you forget to run `npm install`? "
) ,
specifier
) ) ;
}
}
}
}
Ok ( ( ) )
}
2021-11-08 20:26:39 -05:00
}
2023-02-15 11:30:54 -05:00
impl Resolver for CliGraphResolver {
2022-08-24 13:36:05 -04:00
fn default_jsx_import_source ( & self ) -> Option < String > {
2022-11-02 10:47:02 -04:00
self . maybe_default_jsx_import_source . clone ( )
2022-08-24 13:36:05 -04:00
}
2024-04-30 14:12:35 -04:00
fn default_jsx_import_source_types ( & self ) -> Option < String > {
self . maybe_default_jsx_import_source_types . clone ( )
}
2021-11-08 20:26:39 -05:00
fn jsx_import_source_module ( & self ) -> & str {
2022-11-02 10:47:02 -04:00
self
. maybe_jsx_import_source_module
. as_deref ( )
. unwrap_or ( DEFAULT_JSX_IMPORT_SOURCE_MODULE )
2021-11-08 20:26:39 -05:00
}
fn resolve (
& self ,
specifier : & str ,
2023-12-06 19:03:18 -05:00
referrer_range : & deno_graph ::Range ,
2023-10-25 14:39:00 -04:00
mode : ResolutionMode ,
2023-10-20 00:02:08 -04:00
) -> Result < ModuleSpecifier , ResolveError > {
2023-10-25 14:39:00 -04:00
fn to_node_mode ( mode : ResolutionMode ) -> NodeResolutionMode {
match mode {
ResolutionMode ::Execution = > NodeResolutionMode ::Execution ,
ResolutionMode ::Types = > NodeResolutionMode ::Types ,
}
}
2023-12-06 19:03:18 -05:00
let referrer = & referrer_range . specifier ;
2024-03-05 19:23:51 -05:00
let result : Result < _ , ResolveError > = self
2024-07-03 20:54:33 -04:00
. workspace_resolver
2024-01-03 22:50:58 -05:00
. resolve ( specifier , referrer )
2024-07-03 20:54:33 -04:00
. map_err ( | err | match err {
MappedResolutionError ::Specifier ( err ) = > ResolveError ::Specifier ( err ) ,
MappedResolutionError ::ImportMap ( err ) = > {
ResolveError ::Other ( err . into ( ) )
}
} ) ;
let result = match result {
Ok ( resolution ) = > match resolution {
MappedResolution ::Normal ( specifier )
| MappedResolution ::ImportMap ( specifier ) = > Ok ( specifier ) ,
2024-07-17 09:13:22 -04:00
MappedResolution ::WorkspaceNpmPackage {
target_pkg_json : pkg_json ,
sub_path ,
..
} = > self
. node_resolver
. as_ref ( )
. unwrap ( )
. resolve_package_sub_path_from_deno_module (
pkg_json . dir_path ( ) ,
sub_path . as_deref ( ) ,
Some ( referrer ) ,
to_node_mode ( mode ) ,
)
. map_err ( ResolveError ::Other )
. map ( | res | res . into_url ( ) ) ,
2024-07-03 20:54:33 -04:00
// todo(dsherret): for byonm it should do resolution solely based on
// the referrer and not the package.json
MappedResolution ::PackageJson {
dep_result ,
alias ,
sub_path ,
..
} = > {
2024-01-03 22:50:58 -05:00
// found a specifier in the package.json, so mark that
// we need to do an "npm install" later
self . found_package_json_dep_flag . raise ( ) ;
2024-07-03 20:54:33 -04:00
dep_result
. as_ref ( )
. map_err ( | e | ResolveError ::Other ( e . clone ( ) . into ( ) ) )
. and_then ( | dep | match dep {
PackageJsonDepValue ::Req ( req ) = > {
ModuleSpecifier ::parse ( & format! (
" npm:{}{} " ,
req ,
sub_path . map ( | s | format! ( " / {} " , s ) ) . unwrap_or_default ( )
) )
. map_err ( | e | ResolveError ::Other ( e . into ( ) ) )
}
PackageJsonDepValue ::Workspace ( version_req ) = > self
. workspace_resolver
. resolve_workspace_pkg_json_folder_for_pkg_json_dep (
alias ,
version_req ,
)
. map_err ( | e | ResolveError ::Other ( e . into ( ) ) )
. and_then ( | pkg_folder | {
Ok (
self
. node_resolver
. as_ref ( )
. unwrap ( )
. resolve_package_sub_path_from_deno_module (
pkg_folder ,
sub_path . as_deref ( ) ,
2024-07-09 12:15:03 -04:00
Some ( referrer ) ,
2024-07-03 20:54:33 -04:00
to_node_mode ( mode ) ,
) ?
. into_url ( ) ,
)
} ) ,
} )
2024-01-03 22:50:58 -05:00
}
2024-07-03 20:54:33 -04:00
} ,
Err ( err ) = > Err ( err ) ,
} ;
// check if it's an npm specifier that resolves to a workspace member
if let Some ( node_resolver ) = & self . node_resolver {
if let Ok ( specifier ) = & result {
if let Ok ( req_ref ) = NpmPackageReqReference ::from_specifier ( specifier ) {
if let Some ( pkg_folder ) = self
. workspace_resolver
. resolve_workspace_pkg_json_folder_for_npm_specifier ( req_ref . req ( ) )
{
return Ok (
node_resolver
. resolve_package_sub_path_from_deno_module (
pkg_folder ,
req_ref . sub_path ( ) ,
2024-07-09 12:15:03 -04:00
Some ( referrer ) ,
2024-07-03 20:54:33 -04:00
to_node_mode ( mode ) ,
) ?
. into_url ( ) ,
) ;
}
2024-01-03 22:50:58 -05:00
}
2024-07-03 20:54:33 -04:00
}
}
2023-08-17 12:14:22 -04:00
2023-12-06 19:03:18 -05:00
// do sloppy imports resolution if enabled
let result =
if let Some ( sloppy_imports_resolver ) = & self . sloppy_imports_resolver {
result . map ( | specifier | {
sloppy_imports_resolve (
sloppy_imports_resolver ,
specifier ,
referrer_range ,
2024-02-27 13:30:21 -05:00
mode ,
2023-12-06 19:03:18 -05:00
)
} )
} else {
result
} ;
2023-08-17 12:14:22 -04:00
// When the user is vendoring, don't allow them to import directly from the vendor/ directory
// as it might cause them confusion or duplicate dependencies. Additionally, this folder has
// special treatment in the language server so it will definitely cause issues/confusion there
// if they do this.
if let Some ( vendor_specifier ) = & self . maybe_vendor_specifier {
if let Ok ( specifier ) = & result {
if specifier . as_str ( ) . starts_with ( vendor_specifier . as_str ( ) ) {
2023-10-20 00:02:08 -04:00
return Err ( ResolveError ::Other ( anyhow! ( " Importing from the vendor directory is not permitted. Use a remote specifier instead or disable vendoring. " ) ) ) ;
2023-08-17 12:14:22 -04:00
}
}
2023-02-23 17:20:23 -05:00
}
2023-08-17 12:14:22 -04:00
2023-10-25 14:39:00 -04:00
if let Some ( resolver ) =
self . npm_resolver . as_ref ( ) . and_then ( | r | r . as_byonm ( ) )
{
match & result {
Ok ( specifier ) = > {
if let Ok ( npm_req_ref ) =
NpmPackageReqReference ::from_specifier ( specifier )
{
let node_resolver = self . node_resolver . as_ref ( ) . unwrap ( ) ;
2024-03-05 19:23:51 -05:00
return node_resolver
2024-06-06 23:37:53 -04:00
. resolve_req_reference ( & npm_req_ref , referrer , to_node_mode ( mode ) )
2024-03-05 19:23:51 -05:00
. map ( | res | res . into_url ( ) )
. map_err ( | err | err . into ( ) ) ;
2023-10-25 14:39:00 -04:00
}
}
Err ( _ ) = > {
if referrer . scheme ( ) = = " file " {
if let Some ( node_resolver ) = & self . node_resolver {
2024-06-06 23:37:53 -04:00
let node_result =
node_resolver . resolve ( specifier , referrer , to_node_mode ( mode ) ) ;
2023-10-26 21:22:15 -04:00
match node_result {
2024-03-05 19:23:51 -05:00
Ok ( Some ( res ) ) = > {
return Ok ( res . into_url ( ) ) ;
2023-10-26 21:22:15 -04:00
}
Ok ( None ) = > {
self
. check_surface_byonm_node_error (
specifier ,
referrer ,
anyhow! ( " Cannot find \" {} \" " , specifier ) ,
resolver ,
)
. map_err ( ResolveError ::Other ) ? ;
}
Err ( err ) = > {
self
. check_surface_byonm_node_error (
2024-07-16 18:32:41 -04:00
specifier ,
referrer ,
err . into ( ) ,
resolver ,
2023-10-26 21:22:15 -04:00
)
. map_err ( ResolveError ::Other ) ? ;
2023-10-25 14:39:00 -04:00
}
}
}
}
}
}
2024-07-09 09:25:50 -04:00
}
if referrer . scheme ( ) = = " file " {
2024-05-21 12:54:15 -04:00
if let Some ( node_resolver ) = & self . node_resolver {
let node_result = node_resolver . resolve_if_in_npm_package (
specifier ,
referrer ,
to_node_mode ( mode ) ,
) ;
if let Some ( Ok ( Some ( res ) ) ) = node_result {
return Ok ( res . into_url ( ) ) ;
}
}
2023-10-25 14:39:00 -04:00
}
2024-03-05 19:23:51 -05:00
let specifier = result ? ;
match & self . node_resolver {
Some ( node_resolver ) = > node_resolver
. handle_if_in_node_modules ( specifier )
. map_err ( | e | e . into ( ) ) ,
None = > Ok ( specifier ) ,
}
2021-11-08 20:26:39 -05:00
}
}
2023-02-22 14:15:25 -05:00
2023-12-06 19:03:18 -05:00
fn sloppy_imports_resolve (
2023-12-07 15:59:13 -05:00
resolver : & SloppyImportsResolver ,
2023-12-06 19:03:18 -05:00
specifier : ModuleSpecifier ,
referrer_range : & deno_graph ::Range ,
2024-02-27 13:30:21 -05:00
mode : ResolutionMode ,
2023-12-06 19:03:18 -05:00
) -> ModuleSpecifier {
2024-02-27 13:30:21 -05:00
let resolution = resolver . resolve ( & specifier , mode ) ;
if mode . is_types ( ) {
// don't bother warning for types resolution because
// we already probably warned during execution resolution
match resolution {
SloppyImportsResolution ::None ( _ ) = > return specifier , // avoid a clone
_ = > return resolution . into_specifier ( ) . into_owned ( ) ,
}
}
2023-12-06 19:03:18 -05:00
let hint_message = match & resolution {
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::JsToTs ( to_specifier ) = > {
2023-12-06 19:03:18 -05:00
let to_media_type = MediaType ::from_specifier ( to_specifier ) ;
2024-02-27 13:30:21 -05:00
let from_media_type = MediaType ::from_specifier ( & specifier ) ;
2023-12-06 19:03:18 -05:00
format! (
" update {} extension to {} " ,
from_media_type . as_ts_extension ( ) ,
to_media_type . as_ts_extension ( )
)
}
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::NoExtension ( to_specifier ) = > {
2023-12-06 19:03:18 -05:00
let to_media_type = MediaType ::from_specifier ( to_specifier ) ;
format! ( " add {} extension " , to_media_type . as_ts_extension ( ) )
}
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::Directory ( to_specifier ) = > {
2023-12-06 19:03:18 -05:00
let file_name = to_specifier
. path ( )
. rsplit_once ( '/' )
. map ( | ( _ , file_name ) | file_name )
. unwrap_or ( to_specifier . path ( ) ) ;
format! ( " specify path to {} file in directory instead " , file_name )
}
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::None ( _ ) = > return specifier ,
2023-12-06 19:03:18 -05:00
} ;
// show a warning when this happens in order to drive
// the user towards correcting these specifiers
2024-03-07 08:59:57 -05:00
if ! * DENO_DISABLE_PEDANTIC_NODE_WARNINGS {
log ::warn! (
" {} Sloppy module resolution {} \n at {} " ,
crate ::colors ::yellow ( " Warning " ) ,
crate ::colors ::gray ( format! ( " (hint: {} ) " , hint_message ) ) . to_string ( ) ,
if referrer_range . end = = deno_graph ::Position ::zeroed ( ) {
// not worth showing the range in this case
crate ::colors ::cyan ( referrer_range . specifier . as_str ( ) ) . to_string ( )
} else {
format_range_with_colors ( referrer_range )
} ,
) ;
}
2023-12-08 09:57:06 -05:00
resolution . into_specifier ( ) . into_owned ( )
2023-12-06 19:03:18 -05:00
}
2024-06-05 11:04:16 -04:00
#[ derive(Debug) ]
pub struct WorkerCliNpmGraphResolver < ' a > {
npm_resolver : Option < & ' a Arc < dyn CliNpmResolver > > ,
2024-06-11 08:55:12 -04:00
found_package_json_dep_flag : & ' a AtomicFlag ,
2024-06-05 11:04:16 -04:00
bare_node_builtins_enabled : bool ,
}
#[ async_trait(?Send) ]
impl < ' a > deno_graph ::source ::NpmResolver for WorkerCliNpmGraphResolver < ' a > {
2023-02-22 14:15:25 -05:00
fn resolve_builtin_node_module (
& self ,
specifier : & ModuleSpecifier ,
) -> Result < Option < String > , UnknownBuiltInNodeModuleError > {
if specifier . scheme ( ) ! = " node " {
return Ok ( None ) ;
}
let module_name = specifier . path ( ) . to_string ( ) ;
if is_builtin_node_module ( & module_name ) {
Ok ( Some ( module_name ) )
} else {
Err ( UnknownBuiltInNodeModuleError { module_name } )
}
}
2023-10-20 00:02:08 -04:00
fn on_resolve_bare_builtin_node_module (
& self ,
module_name : & str ,
range : & deno_graph ::Range ,
) {
let deno_graph ::Range {
start , specifier , ..
} = range ;
let line = start . line + 1 ;
let column = start . character + 1 ;
2024-03-07 08:59:57 -05:00
if ! * DENO_DISABLE_PEDANTIC_NODE_WARNINGS {
log ::warn! ( " Warning: Resolving \" {module_name} \" as \" node:{module_name} \" at {specifier}:{line}:{column}. If you want to use a built-in Node module, add a \" node: \" prefix. " )
}
2023-10-20 00:02:08 -04:00
}
2024-06-11 08:55:12 -04:00
fn load_and_cache_npm_package_info ( & self , package_name : & str ) {
2024-06-05 11:04:16 -04:00
match self . npm_resolver {
2023-09-30 12:06:38 -04:00
Some ( npm_resolver ) if npm_resolver . as_managed ( ) . is_some ( ) = > {
let npm_resolver = npm_resolver . clone ( ) ;
2024-06-05 11:04:16 -04:00
let package_name = package_name . to_string ( ) ;
2024-06-11 08:55:12 -04:00
deno_core ::unsync ::spawn ( async move {
2023-09-30 12:06:38 -04:00
if let Some ( managed ) = npm_resolver . as_managed ( ) {
2024-06-11 08:55:12 -04:00
let _ignore = managed . cache_package_info ( & package_name ) . await ;
2023-09-30 12:06:38 -04:00
}
2024-06-11 08:55:12 -04:00
} ) ;
2023-09-30 12:06:38 -04:00
}
2024-06-11 08:55:12 -04:00
_ = > { }
2023-02-22 14:15:25 -05:00
}
}
2024-06-05 11:04:16 -04:00
async fn resolve_pkg_reqs (
& self ,
2024-06-11 08:55:12 -04:00
package_reqs : & [ PackageReq ] ,
) -> NpmResolvePkgReqsResult {
2023-09-30 12:06:38 -04:00
match & self . npm_resolver {
2024-06-05 11:04:16 -04:00
Some ( npm_resolver ) = > {
let npm_resolver = match npm_resolver . as_inner ( ) {
InnerCliNpmResolverRef ::Managed ( npm_resolver ) = > npm_resolver ,
// if we are using byonm, then this should never be called because
// we don't use deno_graph's npm resolution in this case
InnerCliNpmResolverRef ::Byonm ( _ ) = > unreachable! ( ) ,
} ;
2024-06-11 08:55:12 -04:00
let top_level_result = if self . found_package_json_dep_flag . is_raised ( ) {
2024-07-02 18:00:16 -04:00
npm_resolver
. ensure_top_level_package_json_install ( )
. await
. map ( | _ | ( ) )
2024-06-11 08:55:12 -04:00
} else {
Ok ( ( ) )
2024-06-05 11:04:16 -04:00
} ;
2024-06-11 08:55:12 -04:00
let result = npm_resolver . add_package_reqs_raw ( package_reqs ) . await ;
NpmResolvePkgReqsResult {
results : result
. results
. into_iter ( )
. map ( | r | {
r . map_err ( | err | match err {
NpmResolutionError ::Registry ( e ) = > {
NpmLoadError ::RegistryInfo ( Arc ::new ( e . into ( ) ) )
}
NpmResolutionError ::Resolution ( e ) = > {
NpmLoadError ::PackageReqResolution ( Arc ::new ( e . into ( ) ) )
}
NpmResolutionError ::DependencyEntry ( e ) = > {
NpmLoadError ::PackageReqResolution ( Arc ::new ( e . into ( ) ) )
}
} )
} )
. collect ( ) ,
dep_graph_result : match top_level_result {
Ok ( ( ) ) = > result . dependencies_result . map_err ( Arc ::new ) ,
Err ( err ) = > Err ( Arc ::new ( err ) ) ,
} ,
}
}
None = > {
let err = Arc ::new ( anyhow! (
" npm specifiers were requested; but --no-npm is specified "
) ) ;
NpmResolvePkgReqsResult {
results : package_reqs
. iter ( )
. map ( | _ | Err ( NpmLoadError ::RegistryInfo ( err . clone ( ) ) ) )
. collect ( ) ,
dep_graph_result : Err ( err ) ,
2023-10-03 19:05:06 -04:00
}
2024-06-05 11:04:16 -04:00
}
2023-04-06 21:41:19 -04:00
}
2023-02-22 14:15:25 -05:00
}
2023-10-20 00:02:08 -04:00
fn enables_bare_builtin_node_module ( & self ) -> bool {
self . bare_node_builtins_enabled
}
2023-02-22 14:15:25 -05:00
}
2023-02-23 12:33:23 -05:00
2023-12-06 19:03:18 -05:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
2023-12-07 15:59:13 -05:00
pub enum SloppyImportsFsEntry {
2023-12-06 19:03:18 -05:00
File ,
Dir ,
}
2023-12-08 09:57:06 -05:00
impl SloppyImportsFsEntry {
pub fn from_fs_stat (
stat : & deno_runtime ::deno_io ::fs ::FsStat ,
) -> Option < SloppyImportsFsEntry > {
if stat . is_file {
Some ( SloppyImportsFsEntry ::File )
} else if stat . is_directory {
Some ( SloppyImportsFsEntry ::Dir )
} else {
None
}
}
}
2023-12-06 19:03:18 -05:00
#[ derive(Debug, PartialEq, Eq) ]
2023-12-07 15:59:13 -05:00
pub enum SloppyImportsResolution < ' a > {
2023-12-06 19:03:18 -05:00
/// No sloppy resolution was found.
None ( & ' a ModuleSpecifier ) ,
/// Ex. `./file.js` to `./file.ts`
JsToTs ( ModuleSpecifier ) ,
/// Ex. `./file` to `./file.ts`
NoExtension ( ModuleSpecifier ) ,
/// Ex. `./dir` to `./dir/index.ts`
Directory ( ModuleSpecifier ) ,
}
2023-12-07 15:59:13 -05:00
impl < ' a > SloppyImportsResolution < ' a > {
2023-12-08 09:57:06 -05:00
pub fn as_specifier ( & self ) -> & ModuleSpecifier {
match self {
Self ::None ( specifier ) = > specifier ,
Self ::JsToTs ( specifier ) = > specifier ,
Self ::NoExtension ( specifier ) = > specifier ,
Self ::Directory ( specifier ) = > specifier ,
}
}
2023-12-06 19:03:18 -05:00
pub fn into_specifier ( self ) -> Cow < ' a , ModuleSpecifier > {
match self {
Self ::None ( specifier ) = > Cow ::Borrowed ( specifier ) ,
Self ::JsToTs ( specifier ) = > Cow ::Owned ( specifier ) ,
Self ::NoExtension ( specifier ) = > Cow ::Owned ( specifier ) ,
Self ::Directory ( specifier ) = > Cow ::Owned ( specifier ) ,
}
}
2023-12-08 09:57:06 -05:00
pub fn as_suggestion_message ( & self ) -> Option < String > {
Some ( format! ( " Maybe {} " , self . as_base_message ( ) ? ) )
}
pub fn as_lsp_quick_fix_message ( & self ) -> Option < String > {
let message = self . as_base_message ( ) ? ;
let mut chars = message . chars ( ) ;
Some ( format! (
" {}{}. " ,
chars . next ( ) . unwrap ( ) . to_uppercase ( ) ,
chars . as_str ( )
) )
}
fn as_base_message ( & self ) -> Option < String > {
2023-12-06 19:03:18 -05:00
match self {
2023-12-08 09:57:06 -05:00
SloppyImportsResolution ::None ( _ ) = > None ,
SloppyImportsResolution ::JsToTs ( specifier ) = > {
let media_type = MediaType ::from_specifier ( specifier ) ;
Some ( format! (
" change the extension to '{}' " ,
media_type . as_ts_extension ( )
) )
}
SloppyImportsResolution ::NoExtension ( specifier ) = > {
let media_type = MediaType ::from_specifier ( specifier ) ;
Some ( format! (
" add a '{}' extension " ,
media_type . as_ts_extension ( )
) )
}
SloppyImportsResolution ::Directory ( specifier ) = > {
let file_name = specifier
. path ( )
. rsplit_once ( '/' )
. map ( | ( _ , file_name ) | file_name )
. unwrap_or ( specifier . path ( ) ) ;
Some ( format! (
" specify path to '{}' file in directory instead " ,
file_name
) )
}
2023-12-06 19:03:18 -05:00
}
}
}
#[ derive(Debug) ]
2023-12-07 15:59:13 -05:00
pub struct SloppyImportsResolver {
2024-05-13 12:55:31 -04:00
fs : Arc < dyn FileSystem > ,
cache : Option < DashMap < PathBuf , Option < SloppyImportsFsEntry > > > ,
2023-12-06 19:03:18 -05:00
}
2023-12-07 15:59:13 -05:00
impl SloppyImportsResolver {
2023-12-06 19:03:18 -05:00
pub fn new ( fs : Arc < dyn FileSystem > ) -> Self {
Self {
2024-05-13 12:55:31 -04:00
fs ,
cache : Some ( Default ::default ( ) ) ,
2023-12-06 19:03:18 -05:00
}
}
2024-05-13 12:55:31 -04:00
pub fn new_without_stat_cache ( fs : Arc < dyn FileSystem > ) -> Self {
Self { fs , cache : None }
2023-12-08 09:57:06 -05:00
}
2024-05-13 12:55:31 -04:00
pub fn resolve < ' a > (
& self ,
specifier : & ' a ModuleSpecifier ,
2024-02-27 13:30:21 -05:00
mode : ResolutionMode ,
2024-05-13 12:55:31 -04:00
) -> SloppyImportsResolution < ' a > {
2024-02-27 13:30:21 -05:00
fn path_without_ext (
path : & Path ,
media_type : MediaType ,
) -> Option < Cow < str > > {
let old_path_str = path . to_string_lossy ( ) ;
match media_type {
MediaType ::Unknown = > Some ( old_path_str ) ,
_ = > old_path_str
. strip_suffix ( media_type . as_ts_extension ( ) )
. map ( | s | Cow ::Owned ( s . to_string ( ) ) ) ,
}
}
fn media_types_to_paths (
path_no_ext : & str ,
probe_media_type_types : Vec < MediaType > ,
2024-03-08 12:25:55 -05:00
reason : SloppyImportsResolutionReason ,
) -> Vec < ( PathBuf , SloppyImportsResolutionReason ) > {
2024-02-27 13:30:21 -05:00
probe_media_type_types
. into_iter ( )
. map ( | media_type | {
2024-03-08 12:25:55 -05:00
(
PathBuf ::from ( format! (
" {}{} " ,
path_no_ext ,
media_type . as_ts_extension ( )
) ) ,
reason ,
)
2024-02-27 13:30:21 -05:00
} )
. collect ::< Vec < _ > > ( )
}
2023-12-06 19:03:18 -05:00
if specifier . scheme ( ) ! = " file " {
2023-12-07 15:59:13 -05:00
return SloppyImportsResolution ::None ( specifier ) ;
2023-12-06 19:03:18 -05:00
}
let Ok ( path ) = specifier_to_file_path ( specifier ) else {
2023-12-07 15:59:13 -05:00
return SloppyImportsResolution ::None ( specifier ) ;
2023-12-06 19:03:18 -05:00
} ;
2024-03-08 12:25:55 -05:00
#[ derive(Clone, Copy) ]
enum SloppyImportsResolutionReason {
JsToTs ,
NoExtension ,
Directory ,
}
let probe_paths : Vec < ( PathBuf , SloppyImportsResolutionReason ) > =
2024-05-13 12:55:31 -04:00
match self . stat_sync ( & path ) {
2024-03-08 12:25:55 -05:00
Some ( SloppyImportsFsEntry ::File ) = > {
if mode . is_types ( ) {
let media_type = MediaType ::from_specifier ( specifier ) ;
// attempt to resolve the .d.ts file before the .js file
let probe_media_type_types = match media_type {
MediaType ::JavaScript = > {
vec! [ ( MediaType ::Dts ) , MediaType ::JavaScript ]
}
MediaType ::Mjs = > {
vec! [ MediaType ::Dmts , MediaType ::Dts , MediaType ::Mjs ]
}
MediaType ::Cjs = > {
vec! [ MediaType ::Dcts , MediaType ::Dts , MediaType ::Cjs ]
}
_ = > return SloppyImportsResolution ::None ( specifier ) ,
} ;
let Some ( path_no_ext ) = path_without_ext ( & path , media_type ) else {
return SloppyImportsResolution ::None ( specifier ) ;
} ;
media_types_to_paths (
& path_no_ext ,
probe_media_type_types ,
SloppyImportsResolutionReason ::JsToTs ,
)
} else {
return SloppyImportsResolution ::None ( specifier ) ;
}
}
entry @ None | entry @ Some ( SloppyImportsFsEntry ::Dir ) = > {
2024-02-27 13:30:21 -05:00
let media_type = MediaType ::from_specifier ( specifier ) ;
let probe_media_type_types = match media_type {
2024-03-08 12:25:55 -05:00
MediaType ::JavaScript = > (
if mode . is_types ( ) {
vec! [ MediaType ::TypeScript , MediaType ::Tsx , MediaType ::Dts ]
} else {
vec! [ MediaType ::TypeScript , MediaType ::Tsx ]
} ,
SloppyImportsResolutionReason ::JsToTs ,
) ,
MediaType ::Jsx = > {
( vec! [ MediaType ::Tsx ] , SloppyImportsResolutionReason ::JsToTs )
2024-02-27 13:30:21 -05:00
}
2024-03-08 12:25:55 -05:00
MediaType ::Mjs = > (
if mode . is_types ( ) {
vec! [ MediaType ::Mts , MediaType ::Dmts , MediaType ::Dts ]
} else {
vec! [ MediaType ::Mts ]
} ,
SloppyImportsResolutionReason ::JsToTs ,
) ,
MediaType ::Cjs = > (
if mode . is_types ( ) {
vec! [ MediaType ::Cts , MediaType ::Dcts , MediaType ::Dts ]
} else {
vec! [ MediaType ::Cts ]
} ,
SloppyImportsResolutionReason ::JsToTs ,
) ,
MediaType ::TypeScript
| MediaType ::Mts
| MediaType ::Cts
| MediaType ::Dts
| MediaType ::Dmts
| MediaType ::Dcts
| MediaType ::Tsx
| MediaType ::Json
| MediaType ::Wasm
| MediaType ::TsBuildInfo
| MediaType ::SourceMap = > {
return SloppyImportsResolution ::None ( specifier )
2024-02-27 13:30:21 -05:00
}
2024-03-08 12:25:55 -05:00
MediaType ::Unknown = > (
if mode . is_types ( ) {
vec! [
MediaType ::TypeScript ,
MediaType ::Tsx ,
MediaType ::Mts ,
MediaType ::Dts ,
MediaType ::Dmts ,
MediaType ::Dcts ,
MediaType ::JavaScript ,
MediaType ::Jsx ,
MediaType ::Mjs ,
]
} else {
vec! [
MediaType ::TypeScript ,
MediaType ::JavaScript ,
MediaType ::Tsx ,
MediaType ::Jsx ,
MediaType ::Mts ,
MediaType ::Mjs ,
]
} ,
SloppyImportsResolutionReason ::NoExtension ,
) ,
2024-02-27 13:30:21 -05:00
} ;
2024-03-08 12:25:55 -05:00
let mut probe_paths = match path_without_ext ( & path , media_type ) {
Some ( path_no_ext ) = > media_types_to_paths (
& path_no_ext ,
probe_media_type_types . 0 ,
probe_media_type_types . 1 ,
) ,
None = > vec! [ ] ,
2024-02-27 13:30:21 -05:00
} ;
2024-03-08 12:25:55 -05:00
if matches! ( entry , Some ( SloppyImportsFsEntry ::Dir ) ) {
// try to resolve at the index file
2024-02-27 13:30:21 -05:00
if mode . is_types ( ) {
2024-03-08 12:25:55 -05:00
probe_paths . push ( (
path . join ( " index.ts " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.mts " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.d.ts " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.d.mts " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.js " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.mjs " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.tsx " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.jsx " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
2024-02-27 13:30:21 -05:00
} else {
2024-03-08 12:25:55 -05:00
probe_paths . push ( (
path . join ( " index.ts " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.mts " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.tsx " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.js " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.mjs " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
probe_paths . push ( (
path . join ( " index.jsx " ) ,
SloppyImportsResolutionReason ::Directory ,
) ) ;
2024-02-27 13:30:21 -05:00
}
}
2024-03-08 12:25:55 -05:00
if probe_paths . is_empty ( ) {
return SloppyImportsResolution ::None ( specifier ) ;
2023-12-06 19:03:18 -05:00
}
2024-03-08 12:25:55 -05:00
probe_paths
}
} ;
2023-12-06 19:03:18 -05:00
2024-03-08 12:25:55 -05:00
for ( probe_path , reason ) in probe_paths {
2024-05-13 12:55:31 -04:00
if self . stat_sync ( & probe_path ) = = Some ( SloppyImportsFsEntry ::File ) {
2023-12-06 19:03:18 -05:00
if let Ok ( specifier ) = ModuleSpecifier ::from_file_path ( probe_path ) {
2024-03-08 12:25:55 -05:00
match reason {
SloppyImportsResolutionReason ::JsToTs = > {
return SloppyImportsResolution ::JsToTs ( specifier )
}
SloppyImportsResolutionReason ::NoExtension = > {
return SloppyImportsResolution ::NoExtension ( specifier )
}
SloppyImportsResolutionReason ::Directory = > {
return SloppyImportsResolution ::Directory ( specifier )
}
2023-12-06 19:03:18 -05:00
}
}
}
}
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::None ( specifier )
2023-12-06 19:03:18 -05:00
}
2024-05-13 12:55:31 -04:00
fn stat_sync ( & self , path : & Path ) -> Option < SloppyImportsFsEntry > {
if let Some ( cache ) = & self . cache {
if let Some ( entry ) = cache . get ( path ) {
return * entry ;
}
}
let entry = self
. fs
. stat_sync ( path )
. ok ( )
. and_then ( | stat | SloppyImportsFsEntry ::from_fs_stat ( & stat ) ) ;
if let Some ( cache ) = & self . cache {
cache . insert ( path . to_owned ( ) , entry ) ;
}
entry
2023-12-06 19:03:18 -05:00
}
}
2023-02-23 12:33:23 -05:00
#[ cfg(test) ]
mod test {
2023-12-06 19:03:18 -05:00
use test_util ::TestContext ;
2023-02-23 12:33:23 -05:00
use super ::* ;
2023-12-06 19:03:18 -05:00
#[ test ]
fn test_unstable_sloppy_imports ( ) {
2023-12-07 15:59:13 -05:00
fn resolve ( specifier : & ModuleSpecifier ) -> SloppyImportsResolution {
2024-05-13 12:55:31 -04:00
SloppyImportsResolver ::new ( Arc ::new ( deno_fs ::RealFs ) )
. resolve ( specifier , ResolutionMode ::Execution )
2023-12-06 19:03:18 -05:00
}
let context = TestContext ::default ( ) ;
let temp_dir = context . temp_dir ( ) . path ( ) ;
// scenarios like resolving ./example.js to ./example.ts
for ( ext_from , ext_to ) in [ ( " js " , " ts " ) , ( " js " , " tsx " ) , ( " mjs " , " mts " ) ] {
let ts_file = temp_dir . join ( format! ( " file. {} " , ext_to ) ) ;
ts_file . write ( " " ) ;
let ts_file_uri = ts_file . uri_file ( ) ;
assert_eq! (
resolve ( & ts_file . uri_file ( ) ) ,
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::None ( & ts_file_uri ) ,
2023-12-06 19:03:18 -05:00
) ;
assert_eq! (
resolve (
& temp_dir
. uri_dir ( )
. join ( & format! ( " file. {} " , ext_from ) )
. unwrap ( )
) ,
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::JsToTs ( ts_file . uri_file ( ) ) ,
2023-12-06 19:03:18 -05:00
) ;
ts_file . remove_file ( ) ;
}
// no extension scenarios
for ext in [ " js " , " ts " , " js " , " tsx " , " jsx " , " mjs " , " mts " ] {
let file = temp_dir . join ( format! ( " file. {} " , ext ) ) ;
file . write ( " " ) ;
assert_eq! (
resolve (
& temp_dir
. uri_dir ( )
. join ( " file " ) // no ext
. unwrap ( )
) ,
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::NoExtension ( file . uri_file ( ) ) ,
2023-12-06 19:03:18 -05:00
) ;
file . remove_file ( ) ;
}
// .ts and .js exists, .js specified (goes to specified)
{
let ts_file = temp_dir . join ( " file.ts " ) ;
ts_file . write ( " " ) ;
let js_file = temp_dir . join ( " file.js " ) ;
js_file . write ( " " ) ;
let js_file_uri = js_file . uri_file ( ) ;
assert_eq! (
resolve ( & js_file . uri_file ( ) ) ,
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::None ( & js_file_uri ) ,
2023-12-06 19:03:18 -05:00
) ;
}
// resolving a directory to an index file
{
let routes_dir = temp_dir . join ( " routes " ) ;
routes_dir . create_dir_all ( ) ;
let index_file = routes_dir . join ( " index.ts " ) ;
index_file . write ( " " ) ;
assert_eq! (
resolve ( & routes_dir . uri_file ( ) ) ,
2023-12-07 15:59:13 -05:00
SloppyImportsResolution ::Directory ( index_file . uri_file ( ) ) ,
2023-12-06 19:03:18 -05:00
) ;
2024-03-08 12:25:55 -05:00
}
// both a directory and a file with specifier is present
{
let api_dir = temp_dir . join ( " api " ) ;
api_dir . create_dir_all ( ) ;
let bar_file = api_dir . join ( " bar.ts " ) ;
bar_file . write ( " " ) ;
let api_file = temp_dir . join ( " api.ts " ) ;
api_file . write ( " " ) ;
assert_eq! (
resolve ( & api_dir . uri_file ( ) ) ,
SloppyImportsResolution ::NoExtension ( api_file . uri_file ( ) ) ,
) ;
2023-12-06 19:03:18 -05:00
}
}
2023-12-08 09:57:06 -05:00
#[ test ]
fn test_sloppy_import_resolution_suggestion_message ( ) {
// none
let url = ModuleSpecifier ::parse ( " file:///dir/index.js " ) . unwrap ( ) ;
assert_eq! (
SloppyImportsResolution ::None ( & url ) . as_suggestion_message ( ) ,
None ,
) ;
// directory
assert_eq! (
SloppyImportsResolution ::Directory (
ModuleSpecifier ::parse ( " file:///dir/index.js " ) . unwrap ( )
)
. as_suggestion_message ( )
. unwrap ( ) ,
" Maybe specify path to 'index.js' file in directory instead "
) ;
// no ext
assert_eq! (
SloppyImportsResolution ::NoExtension (
ModuleSpecifier ::parse ( " file:///dir/index.mjs " ) . unwrap ( )
)
. as_suggestion_message ( )
. unwrap ( ) ,
" Maybe add a '.mjs' extension "
) ;
// js to ts
assert_eq! (
SloppyImportsResolution ::JsToTs (
ModuleSpecifier ::parse ( " file:///dir/index.mts " ) . unwrap ( )
)
. as_suggestion_message ( )
. unwrap ( ) ,
" Maybe change the extension to '.mts' "
) ;
}
2023-02-23 12:33:23 -05:00
}