2023-01-02 21:00:42 +00:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2021-10-10 21:26:22 +00:00
2023-03-03 22:27:05 +00:00
use deno_core ::anyhow ::anyhow ;
2023-01-24 13:23:19 +00:00
use deno_core ::error ::AnyError ;
2023-02-22 19:15:25 +00:00
use deno_core ::futures ::future ;
use deno_core ::futures ::future ::LocalBoxFuture ;
use deno_core ::futures ::FutureExt ;
2021-10-10 21:26:22 +00:00
use deno_core ::ModuleSpecifier ;
2023-09-07 13:09:16 +00:00
use deno_graph ::source ::NpmPackageReqResolution ;
2023-02-22 19:15:25 +00:00
use deno_graph ::source ::NpmResolver ;
2023-10-24 13:37:02 +00:00
use deno_graph ::source ::ResolutionMode ;
2023-10-20 04:02:08 +00:00
use deno_graph ::source ::ResolveError ;
2021-10-10 21:26:22 +00:00
use deno_graph ::source ::Resolver ;
2023-02-22 19:15:25 +00:00
use deno_graph ::source ::UnknownBuiltInNodeModuleError ;
2022-11-02 14:47:02 +00:00
use deno_graph ::source ::DEFAULT_JSX_IMPORT_SOURCE_MODULE ;
2023-02-22 19:15:25 +00:00
use deno_runtime ::deno_node ::is_builtin_node_module ;
2023-08-21 09:53:52 +00:00
use deno_semver ::package ::PackageReq ;
2021-10-10 21:26:22 +00:00
use import_map ::ImportMap ;
2023-08-17 16:14:22 +00:00
use std ::path ::PathBuf ;
2021-11-09 01:26:39 +00:00
use std ::sync ::Arc ;
2021-10-10 21:26:22 +00:00
2023-03-03 22:27:05 +00:00
use crate ::args ::package_json ::PackageJsonDeps ;
2022-11-25 22:00:28 +00:00
use crate ::args ::JsxImportSourceConfig ;
2023-05-11 00:06:59 +00:00
use crate ::args ::PackageJsonDepsProvider ;
2023-09-30 16:06:38 +00:00
use crate ::npm ::CliNpmResolver ;
2023-10-03 23:05:06 +00:00
use crate ::npm ::InnerCliNpmResolverRef ;
2023-04-11 22:10:51 +00:00
use crate ::util ::sync ::AtomicFlag ;
2022-08-24 17:36:05 +00:00
2023-05-11 00:06:59 +00: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 14:47:02 +00:00
/// A resolver that takes care of resolution, taking into account loaded
/// import map, JSX settings.
2023-04-14 20:22:33 +00:00
#[ derive(Debug) ]
2023-02-15 16:30:54 +00:00
pub struct CliGraphResolver {
2023-05-11 00:06:59 +00:00
mapped_specifier_resolver : MappedSpecifierResolver ,
2022-11-02 14:47:02 +00:00
maybe_default_jsx_import_source : Option < String > ,
maybe_jsx_import_source_module : Option < String > ,
2023-08-17 16:14:22 +00:00
maybe_vendor_specifier : Option < ModuleSpecifier > ,
2023-09-30 16:06:38 +00:00
npm_resolver : Option < Arc < dyn CliNpmResolver > > ,
2023-04-11 22:10:51 +00:00
found_package_json_dep_flag : Arc < AtomicFlag > ,
2023-10-20 04:02:08 +00:00
bare_node_builtins_enabled : bool ,
2023-02-22 19:15:25 +00:00
}
2023-08-17 16:14:22 +00:00
pub struct CliGraphResolverOptions < ' a > {
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 04:02:08 +00:00
pub bare_node_builtins_enabled : bool ,
2023-08-17 16:14:22 +00:00
}
2023-02-15 16:30:54 +00:00
impl CliGraphResolver {
pub fn new (
2023-09-30 16:06:38 +00:00
npm_resolver : Option < Arc < dyn CliNpmResolver > > ,
2023-05-11 00:06:59 +00:00
package_json_deps_provider : Arc < PackageJsonDepsProvider > ,
2023-08-17 16:14:22 +00:00
options : CliGraphResolverOptions ,
2023-02-15 16:30:54 +00:00
) -> Self {
Self {
2023-10-03 23:05:06 +00:00
mapped_specifier_resolver : MappedSpecifierResolver ::new (
options . maybe_import_map ,
2023-05-11 00:06:59 +00:00
package_json_deps_provider ,
2023-10-03 23:05:06 +00:00
) ,
2023-08-17 16:14:22 +00:00
maybe_default_jsx_import_source : options
. maybe_jsx_import_source_config
2023-02-15 16:30:54 +00:00
. as_ref ( )
. and_then ( | c | c . default_specifier . clone ( ) ) ,
2023-08-17 16:14:22 +00:00
maybe_jsx_import_source_module : options
. maybe_jsx_import_source_config
2023-02-15 16:30:54 +00:00
. map ( | c | c . module ) ,
2023-08-17 16:14:22 +00:00
maybe_vendor_specifier : options
. maybe_vendor_dir
. and_then ( | v | ModuleSpecifier ::from_directory_path ( v ) . ok ( ) ) ,
2023-09-30 16:06:38 +00:00
npm_resolver ,
2023-04-11 22:10:51 +00:00
found_package_json_dep_flag : Default ::default ( ) ,
2023-10-20 04:02:08 +00:00
bare_node_builtins_enabled : options . bare_node_builtins_enabled ,
2022-01-31 22:33:57 +00:00
}
2021-10-10 21:26:22 +00:00
}
2021-11-09 01:26:39 +00:00
2022-11-02 14:47:02 +00:00
pub fn as_graph_resolver ( & self ) -> & dyn Resolver {
2021-11-09 01:26:39 +00:00
self
}
2023-02-22 19:15:25 +00:00
pub fn as_graph_npm_resolver ( & self ) -> & dyn NpmResolver {
self
}
2023-04-11 22:10:51 +00:00
2023-09-30 16:06:38 +00:00
pub fn found_package_json_dep ( & self ) -> bool {
self . found_package_json_dep_flag . is_raised ( )
2023-04-11 22:10:51 +00:00
}
2021-11-09 01:26:39 +00:00
}
2023-02-15 16:30:54 +00:00
impl Resolver for CliGraphResolver {
2022-08-24 17:36:05 +00:00
fn default_jsx_import_source ( & self ) -> Option < String > {
2022-11-02 14:47:02 +00:00
self . maybe_default_jsx_import_source . clone ( )
2022-08-24 17:36:05 +00:00
}
2021-11-09 01:26:39 +00:00
fn jsx_import_source_module ( & self ) -> & str {
2022-11-02 14:47:02 +00:00
self
. maybe_jsx_import_source_module
. as_deref ( )
. unwrap_or ( DEFAULT_JSX_IMPORT_SOURCE_MODULE )
2021-11-09 01:26:39 +00:00
}
fn resolve (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
2023-10-24 13:37:02 +00:00
_mode : ResolutionMode ,
2023-10-20 04:02:08 +00:00
) -> Result < ModuleSpecifier , ResolveError > {
2023-08-17 16:14:22 +00:00
let result = match self
2023-05-11 00:06:59 +00:00
. mapped_specifier_resolver
. resolve ( specifier , referrer ) ?
2023-02-25 00:35:43 +00:00
{
2023-10-03 23:05:06 +00:00
MappedResolution ::ImportMap ( specifier ) = > Ok ( specifier ) ,
MappedResolution ::PackageJson ( specifier ) = > {
2023-05-11 00:06:59 +00:00
// found a specifier in the package.json, so mark that
// we need to do an "npm install" later
2023-04-11 22:10:51 +00:00
self . found_package_json_dep_flag . raise ( ) ;
2023-05-11 00:06:59 +00:00
Ok ( specifier )
2023-02-23 17:33:23 +00:00
}
2023-10-03 23:05:06 +00:00
MappedResolution ::None = > deno_graph ::resolve_import ( specifier , referrer )
2023-05-11 00:06:59 +00:00
. map_err ( | err | err . into ( ) ) ,
2023-08-17 16:14:22 +00: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 04:02:08 +00: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 16:14:22 +00:00
}
}
2023-02-23 22:20:23 +00:00
}
2023-08-17 16:14:22 +00:00
result
2021-11-09 01:26:39 +00:00
}
}
2023-02-22 19:15:25 +00:00
2023-02-23 17:33:23 +00:00
fn resolve_package_json_dep (
specifier : & str ,
2023-03-03 22:27:05 +00:00
deps : & PackageJsonDeps ,
) -> Result < Option < ModuleSpecifier > , AnyError > {
for ( bare_specifier , req_result ) in deps {
2023-02-23 17:33:23 +00:00
if specifier . starts_with ( bare_specifier ) {
let path = & specifier [ bare_specifier . len ( ) .. ] ;
2023-03-03 22:27:05 +00: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 17:33:23 +00:00
}
}
}
Ok ( None )
}
2023-02-22 19:15:25 +00: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 04:02:08 +00: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 19:15:25 +00:00
fn load_and_cache_npm_package_info (
& self ,
package_name : & str ,
2023-04-12 12:36:11 +00:00
) -> LocalBoxFuture < 'static , Result < ( ) , AnyError > > {
2023-09-30 16:06:38 +00: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 19:15:25 +00:00
}
}
2023-09-07 13:09:16 +00:00
fn resolve_npm ( & self , package_req : & PackageReq ) -> NpmPackageReqResolution {
2023-09-30 16:06:38 +00:00
match & self . npm_resolver {
2023-10-03 23:05:06 +00: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 16:06:38 +00:00
None = > NpmPackageReqResolution ::Err ( anyhow! (
" npm specifiers were requested; but --no-npm is specified "
) ) ,
2023-04-07 01:41:19 +00:00
}
2023-02-22 19:15:25 +00:00
}
2023-10-20 04:02:08 +00:00
fn enables_bare_builtin_node_module ( & self ) -> bool {
self . bare_node_builtins_enabled
}
2023-02-22 19:15:25 +00:00
}
2023-02-23 17:33:23 +00:00
#[ cfg(test) ]
mod test {
2023-03-03 22:27:05 +00:00
use std ::collections ::BTreeMap ;
2023-02-23 17:33:23 +00:00
use super ::* ;
#[ test ]
fn test_resolve_package_json_dep ( ) {
fn resolve (
specifier : & str ,
2023-08-21 09:53:52 +00:00
deps : & BTreeMap < String , PackageReq > ,
2023-02-23 17:33:23 +00:00
) -> Result < Option < String > , String > {
2023-03-03 22:27:05 +00:00
let deps = deps
. iter ( )
. map ( | ( key , value ) | ( key . to_string ( ) , Ok ( value . clone ( ) ) ) )
. collect ( ) ;
resolve_package_json_dep ( specifier , & deps )
2023-02-23 17:33:23 +00: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 09:53:52 +00:00
PackageReq ::from_str ( " package@1.0 " ) . unwrap ( ) ,
2023-02-23 17:33:23 +00:00
) ,
(
" package-alias " . to_string ( ) ,
2023-08-21 09:53:52 +00:00
PackageReq ::from_str ( " package@^1.2 " ) . unwrap ( ) ,
2023-02-23 17:33:23 +00:00
) ,
(
" @deno/test " . to_string ( ) ,
2023-08-21 09:53:52 +00:00
PackageReq ::from_str ( " @deno/test@~0.2 " ) . unwrap ( ) ,
2023-02-23 17:33:23 +00: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 22:27:05 +00:00
Some ( " npm:package@1.0/some_path.ts " . to_string ( ) ) ,
2023-02-23 17:33:23 +00: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 22:27:05 +00:00
Some ( " npm:@deno/test@~0.2/some_path.ts " . to_string ( ) ) ,
2023-02-23 17:33:23 +00: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 ) ;
}
}