2023-01-02 16:00:42 -05:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2021-10-10 17:26:22 -04:00
2023-12-06 19:03:18 -05:00
use deno_ast ::MediaType ;
2023-03-03 17:27:05 -05:00
use deno_core ::anyhow ::anyhow ;
2023-01-24 08:23:19 -05:00
use deno_core ::error ::AnyError ;
2023-02-22 14:15:25 -05:00
use deno_core ::futures ::future ;
use deno_core ::futures ::future ::LocalBoxFuture ;
use deno_core ::futures ::FutureExt ;
2023-12-06 19:03:18 -05:00
use deno_core ::parking_lot ::Mutex ;
2021-10-10 17:26:22 -04:00
use deno_core ::ModuleSpecifier ;
2023-09-07 09:09:16 -04:00
use deno_graph ::source ::NpmPackageReqResolution ;
2023-02-22 14:15:25 -05:00
use deno_graph ::source ::NpmResolver ;
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 ;
2023-10-25 14:39:00 -04:00
use deno_runtime ::deno_fs ::FileSystem ;
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 ;
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 ;
2023-10-25 14:39:00 -04:00
use deno_runtime ::permissions ::PermissionsContainer ;
use deno_semver ::npm ::NpmPackageReqReference ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageReq ;
2021-10-10 17:26:22 -04:00
use import_map ::ImportMap ;
2023-12-06 19:03:18 -05:00
use std ::borrow ::Cow ;
use std ::collections ::HashMap ;
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
2023-03-03 17:27:05 -05:00
use crate ::args ::package_json ::PackageJsonDeps ;
2022-11-25 17:00:28 -05:00
use crate ::args ::JsxImportSourceConfig ;
2023-05-10 20:06:59 -04:00
use crate ::args ::PackageJsonDepsProvider ;
2023-12-06 19:03:18 -05:00
use crate ::graph_util ::format_range_with_colors ;
2023-10-25 14:39:00 -04:00
use crate ::module_loader ::CjsResolutionStore ;
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-12-06 19:03:18 -05:00
use crate ::util ::path ::specifier_to_file_path ;
2023-04-11 18:10:51 -04:00
use crate ::util ::sync ::AtomicFlag ;
2022-08-24 13:36:05 -04:00
2023-05-10 20:06:59 -04:00
/// Result of checking if a specifier is mapped via
/// an import map or package.json.
pub enum MappedResolution {
None ,
PackageJson ( ModuleSpecifier ) ,
ImportMap ( ModuleSpecifier ) ,
}
impl MappedResolution {
pub fn into_specifier ( self ) -> Option < ModuleSpecifier > {
match self {
MappedResolution ::None = > Option ::None ,
MappedResolution ::PackageJson ( specifier ) = > Some ( specifier ) ,
MappedResolution ::ImportMap ( specifier ) = > Some ( specifier ) ,
}
}
}
/// Resolver for specifiers that could be mapped via an
/// import map or package.json.
#[ derive(Debug) ]
pub struct MappedSpecifierResolver {
maybe_import_map : Option < Arc < ImportMap > > ,
package_json_deps_provider : Arc < PackageJsonDepsProvider > ,
}
impl MappedSpecifierResolver {
pub fn new (
maybe_import_map : Option < Arc < ImportMap > > ,
package_json_deps_provider : Arc < PackageJsonDepsProvider > ,
) -> Self {
Self {
maybe_import_map ,
package_json_deps_provider ,
}
}
pub fn resolve (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
) -> Result < MappedResolution , AnyError > {
// attempt to resolve with the import map first
let maybe_import_map_err = match self
. maybe_import_map
. as_ref ( )
. map ( | import_map | import_map . resolve ( specifier , referrer ) )
{
Some ( Ok ( value ) ) = > return Ok ( MappedResolution ::ImportMap ( value ) ) ,
Some ( Err ( err ) ) = > Some ( err ) ,
None = > None ,
} ;
// then with package.json
if let Some ( deps ) = self . package_json_deps_provider . deps ( ) {
if let Some ( specifier ) = resolve_package_json_dep ( specifier , deps ) ? {
return Ok ( MappedResolution ::PackageJson ( specifier ) ) ;
}
}
// otherwise, surface the import map error or try resolving when has no import map
if let Some ( err ) = maybe_import_map_err {
Err ( err . into ( ) )
} else {
Ok ( MappedResolution ::None )
}
}
}
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 {
2023-10-25 14:39:00 -04:00
fs : Arc < dyn FileSystem > ,
2023-12-06 19:03:18 -05:00
sloppy_imports_resolver : Option < UnstableSloppyImportsResolver > ,
2023-05-10 20:06:59 -04:00
mapped_specifier_resolver : MappedSpecifierResolver ,
2022-11-02 10:47:02 -04:00
maybe_default_jsx_import_source : Option < String > ,
maybe_jsx_import_source_module : Option < String > ,
2023-08-17 12:14:22 -04:00
maybe_vendor_specifier : Option < ModuleSpecifier > ,
2023-10-25 14:39:00 -04:00
cjs_resolutions : Option < Arc < CjsResolutionStore > > ,
node_resolver : Option < Arc < NodeResolver > > ,
2023-09-30 12:06:38 -04:00
npm_resolver : Option < Arc < dyn CliNpmResolver > > ,
2023-04-11 18:10:51 -04:00
found_package_json_dep_flag : Arc < 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 > {
2023-10-25 14:39:00 -04:00
pub fs : Arc < dyn FileSystem > ,
pub cjs_resolutions : Option < Arc < CjsResolutionStore > > ,
2023-12-06 19:03:18 -05:00
pub sloppy_imports_resolver : Option < UnstableSloppyImportsResolver > ,
2023-10-25 14:39:00 -04:00
pub node_resolver : Option < Arc < NodeResolver > > ,
pub npm_resolver : Option < Arc < dyn CliNpmResolver > > ,
pub package_json_deps_provider : Arc < PackageJsonDepsProvider > ,
2023-08-17 12:14:22 -04:00
pub maybe_jsx_import_source_config : Option < JsxImportSourceConfig > ,
pub maybe_import_map : Option < Arc < ImportMap > > ,
pub maybe_vendor_dir : Option < & ' a PathBuf > ,
2023-10-20 00:02:08 -04:00
pub bare_node_builtins_enabled : bool ,
2023-08-17 12:14:22 -04:00
}
2023-02-15 11:30:54 -05:00
impl CliGraphResolver {
2023-10-25 14:39:00 -04:00
pub fn new ( options : CliGraphResolverOptions ) -> Self {
let is_byonm = options
. npm_resolver
. as_ref ( )
. map ( | n | n . as_byonm ( ) . is_some ( ) )
. unwrap_or ( false ) ;
2023-02-15 11:30:54 -05:00
Self {
2023-10-25 14:39:00 -04:00
fs : options . fs ,
cjs_resolutions : options . cjs_resolutions ,
2023-12-06 19:03:18 -05:00
sloppy_imports_resolver : options . sloppy_imports_resolver ,
2023-10-03 19:05:06 -04:00
mapped_specifier_resolver : MappedSpecifierResolver ::new (
options . maybe_import_map ,
2023-10-25 14:39:00 -04:00
if is_byonm {
// don't resolve from the root package.json deps for byonm
Arc ::new ( PackageJsonDepsProvider ::new ( None ) )
} else {
options . package_json_deps_provider
} ,
2023-10-03 19:05:06 -04:00
) ,
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 ( ) ) ,
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-10-25 14:39:00 -04:00
node_resolver : options . node_resolver ,
npm_resolver : options . npm_resolver ,
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
pub fn as_graph_npm_resolver ( & self ) -> & dyn NpmResolver {
self
}
2023-04-11 18:10:51 -04:00
2023-09-30 12:06:38 -04:00
pub fn found_package_json_dep ( & self ) -> bool {
self . found_package_json_dep_flag . is_raised ( )
2023-04-11 18:10:51 -04:00
}
2023-10-26 21:22:15 -04:00
fn check_surface_byonm_node_error (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
mode : NodeResolutionMode ,
original_err : AnyError ,
resolver : & ByonmCliNpmResolver ,
) -> Result < ( ) , AnyError > {
if let Ok ( ( pkg_name , _ , _ ) ) = parse_npm_pkg_name ( specifier , referrer ) {
match resolver
. resolve_package_folder_from_package ( & pkg_name , referrer , mode )
{
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
}
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 ;
2023-08-17 12:14:22 -04:00
let result = match self
2023-05-10 20:06:59 -04:00
. mapped_specifier_resolver
. resolve ( specifier , referrer ) ?
2023-02-24 19:35:43 -05:00
{
2023-10-03 19:05:06 -04:00
MappedResolution ::ImportMap ( specifier ) = > Ok ( specifier ) ,
MappedResolution ::PackageJson ( specifier ) = > {
2023-05-10 20:06:59 -04:00
// found a specifier in the package.json, so mark that
// we need to do an "npm install" later
2023-04-11 18:10:51 -04:00
self . found_package_json_dep_flag . raise ( ) ;
2023-05-10 20:06:59 -04:00
Ok ( specifier )
2023-02-23 12:33:23 -05:00
}
2023-12-06 19:03:18 -05:00
MappedResolution ::None = > {
deno_graph ::resolve_import ( specifier , & referrer_range . specifier )
. map_err ( | err | err . into ( ) )
}
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 ,
)
} )
} 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 package_folder = resolver
. resolve_pkg_folder_from_deno_module_req (
npm_req_ref . req ( ) ,
referrer ,
) ? ;
let node_resolver = self . node_resolver . as_ref ( ) . unwrap ( ) ;
let package_json_path = package_folder . join ( " package.json " ) ;
if ! self . fs . exists_sync ( & package_json_path ) {
return Err ( ResolveError ::Other ( anyhow! (
2023-10-26 21:22:15 -04:00
" Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `npm install`? " ,
2023-10-25 14:39:00 -04:00
package_json_path . display ( )
) ) ) ;
}
let maybe_resolution = node_resolver
. resolve_package_subpath_from_deno_module (
& package_folder ,
npm_req_ref . sub_path ( ) ,
referrer ,
to_node_mode ( mode ) ,
& PermissionsContainer ::allow_all ( ) ,
) ? ;
match maybe_resolution {
Some ( resolution ) = > {
if let Some ( cjs_resolutions ) = & self . cjs_resolutions {
if let NodeResolution ::CommonJs ( specifier ) = & resolution {
// remember that this was a common js resolution
cjs_resolutions . insert ( specifier . clone ( ) ) ;
}
}
return Ok ( resolution . into_url ( ) ) ;
}
None = > {
return Err ( ResolveError ::Other ( anyhow! (
" Failed resolving package subpath for '{}' in '{}'. " ,
npm_req_ref ,
package_folder . display ( )
) ) ) ;
}
}
}
}
Err ( _ ) = > {
if referrer . scheme ( ) = = " file " {
if let Some ( node_resolver ) = & self . node_resolver {
let node_result = node_resolver . resolve (
specifier ,
referrer ,
to_node_mode ( mode ) ,
& PermissionsContainer ::allow_all ( ) ,
) ;
2023-10-26 21:22:15 -04:00
match node_result {
Ok ( Some ( resolution ) ) = > {
if let Some ( cjs_resolutions ) = & self . cjs_resolutions {
if let NodeResolution ::CommonJs ( specifier ) = & resolution {
// remember that this was a common js resolution
cjs_resolutions . insert ( specifier . clone ( ) ) ;
}
2023-10-25 14:39:00 -04:00
}
2023-10-26 21:22:15 -04:00
return Ok ( resolution . into_url ( ) ) ;
}
Ok ( None ) = > {
self
. check_surface_byonm_node_error (
specifier ,
referrer ,
to_node_mode ( mode ) ,
anyhow! ( " Cannot find \" {} \" " , specifier ) ,
resolver ,
)
. map_err ( ResolveError ::Other ) ? ;
}
Err ( err ) = > {
self
. check_surface_byonm_node_error (
specifier ,
referrer ,
to_node_mode ( mode ) ,
err ,
resolver ,
)
. map_err ( ResolveError ::Other ) ? ;
2023-10-25 14:39:00 -04:00
}
}
}
}
}
}
}
2023-08-17 12:14:22 -04:00
result
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 (
resolver : & UnstableSloppyImportsResolver ,
specifier : ModuleSpecifier ,
referrer_range : & deno_graph ::Range ,
) -> ModuleSpecifier {
let resolution = resolver . resolve ( & specifier ) ;
let hint_message = match & resolution {
UnstableSloppyImportsResolution ::JsToTs ( to_specifier ) = > {
let from_media_type = MediaType ::from_specifier ( & specifier ) ;
let to_media_type = MediaType ::from_specifier ( to_specifier ) ;
format! (
" update {} extension to {} " ,
from_media_type . as_ts_extension ( ) ,
to_media_type . as_ts_extension ( )
)
}
UnstableSloppyImportsResolution ::NoExtension ( to_specifier ) = > {
let to_media_type = MediaType ::from_specifier ( to_specifier ) ;
format! ( " add {} extension " , to_media_type . as_ts_extension ( ) )
}
UnstableSloppyImportsResolution ::Directory ( to_specifier ) = > {
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 )
}
UnstableSloppyImportsResolution ::None ( _ ) = > return specifier ,
} ;
// show a warning when this happens in order to drive
// the user towards correcting these specifiers
log ::warn! (
" {} Sloppy import resolution {} \n at {} " ,
crate ::colors ::yellow ( " Warning " ) ,
crate ::colors ::gray ( format! ( " (hint: {} ) " , hint_message ) ) ,
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 )
} ,
) ;
resolution . into_owned_specifier ( )
}
2023-02-23 12:33:23 -05:00
fn resolve_package_json_dep (
specifier : & str ,
2023-03-03 17:27:05 -05:00
deps : & PackageJsonDeps ,
) -> Result < Option < ModuleSpecifier > , AnyError > {
for ( bare_specifier , req_result ) in deps {
2023-02-23 12:33:23 -05:00
if specifier . starts_with ( bare_specifier ) {
let path = & specifier [ bare_specifier . len ( ) .. ] ;
2023-03-03 17:27:05 -05:00
if path . is_empty ( ) | | path . starts_with ( '/' ) {
let req = req_result . as_ref ( ) . map_err ( | err | {
anyhow! (
" Parsing version constraints in the application-level package.json is more strict at the moment. \n \n {:#} " ,
err . clone ( )
)
} ) ? ;
return Ok ( Some ( ModuleSpecifier ::parse ( & format! ( " npm: {req} {path} " ) ) ? ) ) ;
2023-02-23 12:33:23 -05:00
}
}
}
Ok ( None )
}
2023-02-22 14:15:25 -05:00
impl NpmResolver for CliGraphResolver {
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 ;
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-02-22 14:15:25 -05:00
fn load_and_cache_npm_package_info (
& self ,
package_name : & str ,
2023-04-12 08:36:11 -04:00
) -> LocalBoxFuture < 'static , Result < ( ) , AnyError > > {
2023-09-30 12:06:38 -04:00
match & self . npm_resolver {
Some ( npm_resolver ) if npm_resolver . as_managed ( ) . is_some ( ) = > {
let package_name = package_name . to_string ( ) ;
let npm_resolver = npm_resolver . clone ( ) ;
async move {
if let Some ( managed ) = npm_resolver . as_managed ( ) {
managed . cache_package_info ( & package_name ) . await ? ;
}
Ok ( ( ) )
}
. boxed ( )
}
_ = > {
// return it succeeded and error at the import site below
Box ::pin ( future ::ready ( Ok ( ( ) ) ) )
}
2023-02-22 14:15:25 -05:00
}
}
2023-09-07 09:09:16 -04:00
fn resolve_npm ( & self , package_req : & PackageReq ) -> NpmPackageReqResolution {
2023-09-30 12:06:38 -04:00
match & self . npm_resolver {
2023-10-03 19:05:06 -04:00
Some ( npm_resolver ) = > match npm_resolver . as_inner ( ) {
InnerCliNpmResolverRef ::Managed ( npm_resolver ) = > {
npm_resolver . resolve_npm_for_deno_graph ( package_req )
}
// 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! ( ) ,
} ,
2023-09-30 12:06:38 -04:00
None = > NpmPackageReqResolution ::Err ( anyhow! (
" npm specifiers were requested; but --no-npm is specified "
) ) ,
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) ]
struct UnstableSloppyImportsStatCache {
fs : Arc < dyn FileSystem > ,
cache : Mutex < HashMap < PathBuf , Option < UnstableSloppyImportsFsEntry > > > ,
}
impl UnstableSloppyImportsStatCache {
pub fn new ( fs : Arc < dyn FileSystem > ) -> Self {
Self {
fs ,
cache : Default ::default ( ) ,
}
}
pub fn stat_sync ( & self , path : & Path ) -> Option < UnstableSloppyImportsFsEntry > {
// there will only ever be one thread in here at a
// time, so it's ok to hold the lock for so long
let mut cache = self . cache . lock ( ) ;
if let Some ( entry ) = cache . get ( path ) {
return * entry ;
}
let entry = self . fs . stat_sync ( path ) . ok ( ) . and_then ( | stat | {
if stat . is_file {
Some ( UnstableSloppyImportsFsEntry ::File )
} else if stat . is_directory {
Some ( UnstableSloppyImportsFsEntry ::Dir )
} else {
None
}
} ) ;
cache . insert ( path . to_owned ( ) , entry ) ;
entry
}
}
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
pub enum UnstableSloppyImportsFsEntry {
File ,
Dir ,
}
#[ derive(Debug, PartialEq, Eq) ]
pub enum UnstableSloppyImportsResolution < ' a > {
/// 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 ) ,
}
impl < ' a > UnstableSloppyImportsResolution < ' a > {
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 ) ,
}
}
pub fn into_owned_specifier ( self ) -> ModuleSpecifier {
match self {
Self ::None ( specifier ) = > specifier . clone ( ) ,
Self ::JsToTs ( specifier ) = > specifier ,
Self ::NoExtension ( specifier ) = > specifier ,
Self ::Directory ( specifier ) = > specifier ,
}
}
}
#[ derive(Debug) ]
pub struct UnstableSloppyImportsResolver {
stat_cache : UnstableSloppyImportsStatCache ,
}
impl UnstableSloppyImportsResolver {
pub fn new ( fs : Arc < dyn FileSystem > ) -> Self {
Self {
stat_cache : UnstableSloppyImportsStatCache ::new ( fs ) ,
}
}
pub fn resolve_with_stat_sync (
specifier : & ModuleSpecifier ,
stat_sync : impl Fn ( & Path ) -> Option < UnstableSloppyImportsFsEntry > ,
) -> UnstableSloppyImportsResolution {
if specifier . scheme ( ) ! = " file " {
return UnstableSloppyImportsResolution ::None ( specifier ) ;
}
let Ok ( path ) = specifier_to_file_path ( specifier ) else {
return UnstableSloppyImportsResolution ::None ( specifier ) ;
} ;
let mut is_dir_resolution = false ;
let mut is_no_ext_resolution = false ;
let probe_paths = match ( stat_sync ) ( & path ) {
Some ( UnstableSloppyImportsFsEntry ::File ) = > {
return UnstableSloppyImportsResolution ::None ( specifier ) ;
}
Some ( UnstableSloppyImportsFsEntry ::Dir ) = > {
is_dir_resolution = true ;
// try to resolve at the index file
vec! [
path . join ( " index.ts " ) ,
path . join ( " index.js " ) ,
path . join ( " index.mts " ) ,
path . join ( " index.mjs " ) ,
path . join ( " index.tsx " ) ,
path . join ( " index.jsx " ) ,
]
}
None = > {
let media_type = MediaType ::from_specifier ( specifier ) ;
let probe_media_type_types = match media_type {
MediaType ::JavaScript = > vec! [ MediaType ::TypeScript , MediaType ::Tsx ] ,
MediaType ::Jsx = > vec! [ MediaType ::Tsx ] ,
MediaType ::Mjs = > vec! [ MediaType ::Mts ] ,
MediaType ::Cjs = > vec! [ MediaType ::Cts ] ,
MediaType ::TypeScript
| MediaType ::Mts
| MediaType ::Cts
| MediaType ::Dts
| MediaType ::Dmts
| MediaType ::Dcts
| MediaType ::Tsx
| MediaType ::Json
| MediaType ::Wasm
| MediaType ::TsBuildInfo
| MediaType ::SourceMap = > {
return UnstableSloppyImportsResolution ::None ( specifier )
}
MediaType ::Unknown = > {
is_no_ext_resolution = true ;
vec! [
MediaType ::TypeScript ,
MediaType ::JavaScript ,
MediaType ::Tsx ,
MediaType ::Jsx ,
MediaType ::Mts ,
MediaType ::Mjs ,
]
}
} ;
let old_path_str = path . to_string_lossy ( ) ;
let old_path_str = match media_type {
MediaType ::Unknown = > old_path_str ,
_ = > match old_path_str . strip_suffix ( media_type . as_ts_extension ( ) ) {
Some ( s ) = > Cow ::Borrowed ( s ) ,
None = > return UnstableSloppyImportsResolution ::None ( specifier ) ,
} ,
} ;
probe_media_type_types
. into_iter ( )
. map ( | media_type | {
PathBuf ::from ( format! (
" {}{} " ,
old_path_str ,
media_type . as_ts_extension ( )
) )
} )
. collect ::< Vec < _ > > ( )
}
} ;
for probe_path in probe_paths {
if ( stat_sync ) ( & probe_path ) = = Some ( UnstableSloppyImportsFsEntry ::File ) {
if let Ok ( specifier ) = ModuleSpecifier ::from_file_path ( probe_path ) {
if is_dir_resolution {
return UnstableSloppyImportsResolution ::Directory ( specifier ) ;
} else if is_no_ext_resolution {
return UnstableSloppyImportsResolution ::NoExtension ( specifier ) ;
} else {
return UnstableSloppyImportsResolution ::JsToTs ( specifier ) ;
}
}
}
}
UnstableSloppyImportsResolution ::None ( specifier )
}
pub fn resolve < ' a > (
& self ,
specifier : & ' a ModuleSpecifier ,
) -> UnstableSloppyImportsResolution < ' a > {
Self ::resolve_with_stat_sync ( specifier , | path | {
self . stat_cache . stat_sync ( path )
} )
}
}
2023-02-23 12:33:23 -05:00
#[ cfg(test) ]
mod test {
2023-03-03 17:27:05 -05:00
use std ::collections ::BTreeMap ;
2023-12-06 19:03:18 -05:00
use deno_runtime ::deno_fs ::RealFs ;
use test_util ::TestContext ;
2023-02-23 12:33:23 -05:00
use super ::* ;
#[ test ]
fn test_resolve_package_json_dep ( ) {
fn resolve (
specifier : & str ,
2023-08-21 05:53:52 -04:00
deps : & BTreeMap < String , PackageReq > ,
2023-02-23 12:33:23 -05:00
) -> Result < Option < String > , String > {
2023-03-03 17:27:05 -05:00
let deps = deps
. iter ( )
. map ( | ( key , value ) | ( key . to_string ( ) , Ok ( value . clone ( ) ) ) )
. collect ( ) ;
resolve_package_json_dep ( specifier , & deps )
2023-02-23 12:33:23 -05:00
. map ( | s | s . map ( | s | s . to_string ( ) ) )
. map_err ( | err | err . to_string ( ) )
}
let deps = BTreeMap ::from ( [
(
" package " . to_string ( ) ,
2023-08-21 05:53:52 -04:00
PackageReq ::from_str ( " package@1.0 " ) . unwrap ( ) ,
2023-02-23 12:33:23 -05:00
) ,
(
" package-alias " . to_string ( ) ,
2023-08-21 05:53:52 -04:00
PackageReq ::from_str ( " package@^1.2 " ) . unwrap ( ) ,
2023-02-23 12:33:23 -05:00
) ,
(
" @deno/test " . to_string ( ) ,
2023-08-21 05:53:52 -04:00
PackageReq ::from_str ( " @deno/test@~0.2 " ) . unwrap ( ) ,
2023-02-23 12:33:23 -05:00
) ,
] ) ;
assert_eq! (
resolve ( " package " , & deps ) . unwrap ( ) ,
Some ( " npm:package@1.0 " . to_string ( ) ) ,
) ;
assert_eq! (
resolve ( " package/some_path.ts " , & deps ) . unwrap ( ) ,
2023-03-03 17:27:05 -05:00
Some ( " npm:package@1.0/some_path.ts " . to_string ( ) ) ,
2023-02-23 12:33:23 -05:00
) ;
assert_eq! (
resolve ( " @deno/test " , & deps ) . unwrap ( ) ,
Some ( " npm:@deno/test@~0.2 " . to_string ( ) ) ,
) ;
assert_eq! (
resolve ( " @deno/test/some_path.ts " , & deps ) . unwrap ( ) ,
2023-03-03 17:27:05 -05:00
Some ( " npm:@deno/test@~0.2/some_path.ts " . to_string ( ) ) ,
2023-02-23 12:33:23 -05:00
) ;
// matches the start, but doesn't have the same length or a path
assert_eq! ( resolve ( " @deno/testing " , & deps ) . unwrap ( ) , None , ) ;
// alias
assert_eq! (
resolve ( " package-alias " , & deps ) . unwrap ( ) ,
Some ( " npm:package@^1.2 " . to_string ( ) ) ,
) ;
// non-existent bare specifier
assert_eq! ( resolve ( " non-existent " , & deps ) . unwrap ( ) , None ) ;
}
2023-12-06 19:03:18 -05:00
#[ test ]
fn test_unstable_sloppy_imports ( ) {
fn resolve ( specifier : & ModuleSpecifier ) -> UnstableSloppyImportsResolution {
UnstableSloppyImportsResolver ::resolve_with_stat_sync ( specifier , | path | {
RealFs . stat_sync ( path ) . ok ( ) . and_then ( | stat | {
if stat . is_file {
Some ( UnstableSloppyImportsFsEntry ::File )
} else if stat . is_directory {
Some ( UnstableSloppyImportsFsEntry ::Dir )
} else {
None
}
} )
} )
}
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 ( ) ) ,
UnstableSloppyImportsResolution ::None ( & ts_file_uri ) ,
) ;
assert_eq! (
resolve (
& temp_dir
. uri_dir ( )
. join ( & format! ( " file. {} " , ext_from ) )
. unwrap ( )
) ,
UnstableSloppyImportsResolution ::JsToTs ( ts_file . uri_file ( ) ) ,
) ;
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 ( )
) ,
UnstableSloppyImportsResolution ::NoExtension ( file . uri_file ( ) ) ,
) ;
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 ( ) ) ,
UnstableSloppyImportsResolution ::None ( & js_file_uri ) ,
) ;
}
// 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 ( ) ) ,
UnstableSloppyImportsResolution ::Directory ( index_file . uri_file ( ) ) ,
) ;
}
}
2023-02-23 12:33:23 -05:00
}