2019-06-12 19:55:59 -04:00
use deno ::ModuleSpecifier ;
2019-06-09 09:08:20 -04:00
use indexmap ::IndexMap ;
use serde_json ::Map ;
use serde_json ::Value ;
use std ::cmp ::Ordering ;
2019-07-10 18:53:48 -04:00
use std ::error ::Error ;
use std ::fmt ;
2019-06-09 09:08:20 -04:00
use std ::fs ;
use url ::Url ;
#[ derive(Debug) ]
pub struct ImportMapError {
pub msg : String ,
}
impl ImportMapError {
pub fn new ( msg : & str ) -> Self {
ImportMapError {
msg : msg . to_string ( ) ,
}
}
}
2019-07-10 18:53:48 -04:00
impl fmt ::Display for ImportMapError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
f . pad ( & self . msg )
}
}
impl Error for ImportMapError { }
2019-06-09 09:08:20 -04:00
// NOTE: here is difference between deno and reference implementation - deno currently
// can't resolve URL with other schemes (eg. data:, about:, blob:)
const SUPPORTED_FETCH_SCHEMES : [ & str ; 3 ] = [ " http " , " https " , " file " ] ;
2019-06-12 15:00:08 -04:00
type SpecifierMap = IndexMap < String , Vec < ModuleSpecifier > > ;
2019-06-09 09:08:20 -04:00
type ScopesMap = IndexMap < String , SpecifierMap > ;
#[ derive(Debug) ]
pub struct ImportMap {
base_url : String ,
imports : SpecifierMap ,
scopes : ScopesMap ,
}
impl ImportMap {
pub fn load ( base_url : & str , file_name : & str ) -> Result < Self , ImportMapError > {
let cwd = std ::env ::current_dir ( ) . unwrap ( ) ;
let resolved_path = cwd . join ( file_name ) ;
debug! (
" Attempt to load import map: {} " ,
resolved_path . to_str ( ) . unwrap ( )
) ;
// Load the contents of import map
match fs ::read_to_string ( & resolved_path ) {
Ok ( json_string ) = > ImportMap ::from_json ( base_url , & json_string ) ,
_ = > panic! (
" Error retrieving import map file at \" {} \" " ,
resolved_path . to_str ( ) . unwrap ( )
) ,
}
}
pub fn from_json (
base_url : & str ,
json_string : & str ,
) -> Result < Self , ImportMapError > {
let v : Value = match serde_json ::from_str ( json_string ) {
Ok ( v ) = > v ,
Err ( _ ) = > {
return Err ( ImportMapError ::new ( " Unable to parse import map JSON " ) ) ;
}
} ;
match v {
Value ::Object ( _ ) = > { }
_ = > {
return Err ( ImportMapError ::new ( " Import map JSON must be an object " ) ) ;
}
}
let normalized_imports = match & v . get ( " imports " ) {
Some ( imports_map ) = > {
if ! imports_map . is_object ( ) {
return Err ( ImportMapError ::new (
" Import map's 'imports' must be an object " ,
) ) ;
}
let imports_map = imports_map . as_object ( ) . unwrap ( ) ;
ImportMap ::parse_specifier_map ( imports_map , base_url )
}
None = > IndexMap ::new ( ) ,
} ;
let normalized_scopes = match & v . get ( " scopes " ) {
Some ( scope_map ) = > {
if ! scope_map . is_object ( ) {
return Err ( ImportMapError ::new (
" Import map's 'scopes' must be an object " ,
) ) ;
}
let scope_map = scope_map . as_object ( ) . unwrap ( ) ;
ImportMap ::parse_scope_map ( scope_map , base_url ) ?
}
None = > IndexMap ::new ( ) ,
} ;
let import_map = ImportMap {
base_url : base_url . to_string ( ) ,
imports : normalized_imports ,
scopes : normalized_scopes ,
} ;
Ok ( import_map )
}
fn try_url_like_specifier ( specifier : & str , base : & str ) -> Option < Url > {
// this should never fail
if specifier . starts_with ( '/' )
| | specifier . starts_with ( " ./ " )
| | specifier . starts_with ( " ../ " )
{
let base_url = Url ::parse ( base ) . unwrap ( ) ;
let url = base_url . join ( specifier ) . unwrap ( ) ;
return Some ( url ) ;
}
if let Ok ( url ) = Url ::parse ( specifier ) {
if SUPPORTED_FETCH_SCHEMES . contains ( & url . scheme ( ) ) {
return Some ( url ) ;
}
}
None
}
/// Parse provided key as import map specifier.
///
/// Specifiers must be valid URLs (eg. "https://deno.land/x/std/testing/mod.ts")
/// or "bare" specifiers (eg. "moment").
// TODO: add proper error handling: https://github.com/WICG/import-maps/issues/100
fn normalize_specifier_key (
specifier_key : & str ,
base_url : & str ,
) -> Option < String > {
// ignore empty keys
if specifier_key . is_empty ( ) {
return None ;
}
if let Some ( url ) =
ImportMap ::try_url_like_specifier ( specifier_key , base_url )
{
return Some ( url . to_string ( ) ) ;
}
// "bare" specifier
Some ( specifier_key . to_string ( ) )
}
/// Parse provided addresses as valid URLs.
///
/// Non-valid addresses are skipped.
fn normalize_addresses (
specifier_key : & str ,
base_url : & str ,
potential_addresses : Vec < String > ,
2019-06-12 15:00:08 -04:00
) -> Vec < ModuleSpecifier > {
let mut normalized_addresses : Vec < ModuleSpecifier > = vec! [ ] ;
2019-06-09 09:08:20 -04:00
for potential_address in potential_addresses {
let url =
match ImportMap ::try_url_like_specifier ( & potential_address , base_url ) {
Some ( url ) = > url ,
None = > continue ,
} ;
let url_string = url . to_string ( ) ;
if specifier_key . ends_with ( '/' ) & & ! url_string . ends_with ( '/' ) {
eprintln! (
" Invalid target address {:?} for package specifier {:?}. \
Package address targets must end with \ " / \" . " ,
url_string , specifier_key
) ;
continue ;
}
core: clearly define when module lookup is path-based vs URL-based
The rules are now as follows:
* In `import` statements, as mandated by the WHATWG specification,
the import specifier is always treated as a URL.
If it is a relative URL, it must start with either / or ./ or ../
* A script name passed to deno as a command line argument may be either
an absolute URL or a local path.
- If the name starts with a valid URI scheme followed by a colon, e.g.
'http:', 'https:', 'file:', 'foo+bar:', it always interpreted as a
URL (even if Deno doesn't support the indicated protocol).
- Otherwise, the script name is interpreted as a local path. The local
path may be relative, and operating system semantics determine how
it is resolved. Prefixing a relative path with ./ is not required.
2019-07-08 03:55:24 -04:00
normalized_addresses . push ( url . into ( ) ) ;
2019-06-09 09:08:20 -04:00
}
normalized_addresses
}
/// Convert provided JSON map to valid SpecifierMap.
///
/// From specification:
/// - order of iteration must be retained
/// - SpecifierMap's keys are sorted in longest and alphabetic order
fn parse_specifier_map (
json_map : & Map < String , Value > ,
base_url : & str ,
) -> SpecifierMap {
let mut normalized_map : SpecifierMap = SpecifierMap ::new ( ) ;
// Order is preserved because of "preserve_order" feature of "serde_json".
for ( specifier_key , value ) in json_map . iter ( ) {
let normalized_specifier_key =
match ImportMap ::normalize_specifier_key ( specifier_key , base_url ) {
Some ( s ) = > s ,
None = > continue ,
} ;
let potential_addresses : Vec < String > = match value {
Value ::String ( address ) = > vec! [ address . to_string ( ) ] ,
Value ::Array ( address_array ) = > {
let mut string_addresses : Vec < String > = vec! [ ] ;
for address in address_array {
match address {
Value ::String ( address ) = > {
string_addresses . push ( address . to_string ( ) )
}
_ = > continue ,
}
}
string_addresses
}
Value ::Null = > vec! [ ] ,
_ = > vec! [ ] ,
} ;
let normalized_address_array = ImportMap ::normalize_addresses (
& normalized_specifier_key ,
base_url ,
potential_addresses ,
) ;
debug! (
" normalized specifier {:?}; {:?} " ,
normalized_specifier_key , normalized_address_array
) ;
normalized_map . insert ( normalized_specifier_key , normalized_address_array ) ;
}
// Sort in longest and alphabetical order.
normalized_map . sort_by ( | k1 , _v1 , k2 , _v2 | {
if k1 . len ( ) > k2 . len ( ) {
return Ordering ::Less ;
} else if k2 . len ( ) > k1 . len ( ) {
return Ordering ::Greater ;
}
k2 . cmp ( k1 )
} ) ;
normalized_map
}
/// Convert provided JSON map to valid ScopeMap.
///
/// From specification:
/// - order of iteration must be retained
/// - ScopeMap's keys are sorted in longest and alphabetic order
fn parse_scope_map (
scope_map : & Map < String , Value > ,
base_url : & str ,
) -> Result < ScopesMap , ImportMapError > {
let mut normalized_map : ScopesMap = ScopesMap ::new ( ) ;
// Order is preserved because of "preserve_order" feature of "serde_json".
for ( scope_prefix , potential_specifier_map ) in scope_map . iter ( ) {
if ! potential_specifier_map . is_object ( ) {
return Err ( ImportMapError ::new ( & format! (
" The value for the {:?} scope prefix must be an object " ,
scope_prefix
) ) ) ;
}
let potential_specifier_map =
potential_specifier_map . as_object ( ) . unwrap ( ) ;
let scope_prefix_url =
match Url ::parse ( base_url ) . unwrap ( ) . join ( scope_prefix ) {
Ok ( url ) = > {
if ! SUPPORTED_FETCH_SCHEMES . contains ( & url . scheme ( ) ) {
eprintln! (
" Invalid scope {:?}. Scope URLs must have a valid fetch scheme. " ,
url . to_string ( )
) ;
continue ;
}
url . to_string ( )
}
_ = > continue ,
} ;
let norm_map =
ImportMap ::parse_specifier_map ( potential_specifier_map , base_url ) ;
normalized_map . insert ( scope_prefix_url , norm_map ) ;
}
// Sort in longest and alphabetical order.
normalized_map . sort_by ( | k1 , _v1 , k2 , _v2 | {
if k1 . len ( ) > k2 . len ( ) {
return Ordering ::Less ;
} else if k2 . len ( ) > k1 . len ( ) {
return Ordering ::Greater ;
}
k2 . cmp ( k1 )
} ) ;
Ok ( normalized_map )
}
pub fn resolve_scopes_match (
scopes : & ScopesMap ,
normalized_specifier : & str ,
referrer : & str ,
2019-06-12 15:00:08 -04:00
) -> Result < Option < ModuleSpecifier > , ImportMapError > {
2019-06-09 09:08:20 -04:00
// exact-match
if let Some ( scope_imports ) = scopes . get ( referrer ) {
if let Ok ( scope_match ) =
ImportMap ::resolve_imports_match ( scope_imports , normalized_specifier )
{
// Return only if there was actual match (not None).
if scope_match . is_some ( ) {
return Ok ( scope_match ) ;
}
}
}
for ( normalized_scope_key , scope_imports ) in scopes . iter ( ) {
if normalized_scope_key . ends_with ( '/' )
& & referrer . starts_with ( normalized_scope_key )
{
if let Ok ( scope_match ) =
ImportMap ::resolve_imports_match ( scope_imports , normalized_specifier )
{
// Return only if there was actual match (not None).
if scope_match . is_some ( ) {
return Ok ( scope_match ) ;
}
}
}
}
Ok ( None )
}
// TODO: https://github.com/WICG/import-maps/issues/73#issuecomment-439327758
// for some more optimized candidate implementations.
pub fn resolve_imports_match (
imports : & SpecifierMap ,
normalized_specifier : & str ,
2019-06-12 15:00:08 -04:00
) -> Result < Option < ModuleSpecifier > , ImportMapError > {
2019-06-09 09:08:20 -04:00
// exact-match
if let Some ( address_vec ) = imports . get ( normalized_specifier ) {
if address_vec . is_empty ( ) {
return Err ( ImportMapError ::new ( & format! (
" Specifier {:?} was mapped to no addresses. " ,
normalized_specifier
) ) ) ;
} else if address_vec . len ( ) = = 1 {
let address = address_vec . first ( ) . unwrap ( ) ;
debug! (
" Specifier {:?} was mapped to {:?}. " ,
normalized_specifier , address
) ;
2019-06-12 15:00:08 -04:00
return Ok ( Some ( address . clone ( ) ) ) ;
2019-06-09 09:08:20 -04:00
} else {
return Err ( ImportMapError ::new (
" Multi-address mappings are not yet supported " ,
) ) ;
}
}
// package-prefix match
// "most-specific wins", i.e. when there are multiple matching keys,
// choose the longest.
// https://github.com/WICG/import-maps/issues/102
for ( specifier_key , address_vec ) in imports . iter ( ) {
if specifier_key . ends_with ( '/' )
& & normalized_specifier . starts_with ( specifier_key )
{
if address_vec . is_empty ( ) {
return Err ( ImportMapError ::new ( & format! ( " Specifier {:?} was mapped to no addresses (via prefix specifier key {:?} ). " , normalized_specifier , specifier_key ) ) ) ;
} else if address_vec . len ( ) = = 1 {
let address = address_vec . first ( ) . unwrap ( ) ;
let after_prefix = & normalized_specifier [ specifier_key . len ( ) .. ] ;
2019-07-08 17:04:07 -04:00
let base_url = address . as_url ( ) ;
2019-06-12 15:00:08 -04:00
if let Ok ( url ) = base_url . join ( after_prefix ) {
debug! ( " Specifier {:?} was mapped to {:?} (via prefix specifier key {:?}). " , normalized_specifier , url , address ) ;
return Ok ( Some ( ModuleSpecifier ::from ( url ) ) ) ;
2019-06-09 09:08:20 -04:00
}
unreachable! ( ) ;
} else {
return Err ( ImportMapError ::new (
" Multi-address mappings are not yet supported " ,
) ) ;
}
}
}
debug! (
" Specifier {:?} was not mapped in import map. " ,
normalized_specifier
) ;
Ok ( None )
}
// TODO: add support for built-in modules
/// Currently we support two types of specifiers: URL (http://, https://, file://)
/// and "bare" (moment, jquery, lodash)
///
/// Scenarios:
/// 1. import resolved using import map -> String
/// 2. import restricted by import map -> ImportMapError
/// 3. import not mapped -> None
pub fn resolve (
& self ,
specifier : & str ,
referrer : & str ,
2019-06-12 15:00:08 -04:00
) -> Result < Option < ModuleSpecifier > , ImportMapError > {
2019-06-09 09:08:20 -04:00
let resolved_url : Option < Url > =
ImportMap ::try_url_like_specifier ( specifier , referrer ) ;
let normalized_specifier = match & resolved_url {
Some ( url ) = > url . to_string ( ) ,
None = > specifier . to_string ( ) ,
} ;
let scopes_match = ImportMap ::resolve_scopes_match (
& self . scopes ,
& normalized_specifier ,
& referrer . to_string ( ) ,
) ? ;
// match found in scopes map
if scopes_match . is_some ( ) {
return Ok ( scopes_match ) ;
}
let imports_match =
ImportMap ::resolve_imports_match ( & self . imports , & normalized_specifier ) ? ;
// match found in import map
if imports_match . is_some ( ) {
return Ok ( imports_match ) ;
}
// no match in import map but we got resolvable URL
if let Some ( resolved_url ) = resolved_url {
2019-06-12 15:00:08 -04:00
return Ok ( Some ( ModuleSpecifier ::from ( resolved_url ) ) ) ;
2019-06-09 09:08:20 -04:00
}
Err ( ImportMapError ::new ( & format! (
" Unmapped bare specifier {:?} " ,
normalized_specifier
) ) )
}
}
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn from_json_1 ( ) {
let base_url = " https://deno.land " ;
// empty JSON
assert! ( ImportMap ::from_json ( base_url , " {} " ) . is_ok ( ) ) ;
let non_object_strings = vec! [ " null " , " true " , " 1 " , " \" foo \" " , " [] " ] ;
// invalid JSON
for non_object in non_object_strings . to_vec ( ) {
assert! ( ImportMap ::from_json ( base_url , non_object ) . is_err ( ) ) ;
}
// invalid schema: 'imports' is non-object
for non_object in non_object_strings . to_vec ( ) {
2019-07-31 17:11:37 -04:00
assert! ( ImportMap ::from_json (
base_url ,
& format! ( " {{ \" imports \" : {} }} " , non_object ) ,
)
. is_err ( ) ) ;
2019-06-09 09:08:20 -04:00
}
// invalid schema: 'scopes' is non-object
for non_object in non_object_strings . to_vec ( ) {
2019-07-31 17:11:37 -04:00
assert! ( ImportMap ::from_json (
base_url ,
& format! ( " {{ \" scopes \" : {} }} " , non_object ) ,
)
. is_err ( ) ) ;
2019-06-09 09:08:20 -04:00
}
}
#[ test ]
fn from_json_2 ( ) {
let json_map = r #" {
" imports " : {
" foo " : " https://example.com/1 " ,
" bar " : [ " https://example.com/2 " ] ,
" fizz " : null
}
} " #;
let result = ImportMap ::from_json ( " https://deno.land " , json_map ) ;
assert! ( result . is_ok ( ) ) ;
}
#[ test ]
fn parse_specifier_keys_relative ( ) {
// Should absolutize strings prefixed with ./, ../, or / into the corresponding URLs..
let json_map = r #" {
" imports " : {
" ./foo " : " /dotslash " ,
" ../foo " : " /dotdotslash " ,
" /foo " : " /slash "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map
. imports
. get ( " https://base.example/path1/path2/foo " )
. unwrap ( ) [ 0 ] ,
" https://base.example/dotslash " . to_string ( )
) ;
assert_eq! (
import_map
. imports
. get ( " https://base.example/path1/foo " )
. unwrap ( ) [ 0 ] ,
" https://base.example/dotdotslash " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://base.example/foo " ) . unwrap ( ) [ 0 ] ,
" https://base.example/slash " . to_string ( )
) ;
// Should absolutize the literal strings ./, ../, or / with no suffix..
let json_map = r #" {
" imports " : {
" ./ " : " /dotslash/ " ,
" ../ " : " /dotdotslash/ " ,
" / " : " /slash/ "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map
. imports
. get ( " https://base.example/path1/path2/ " )
. unwrap ( ) [ 0 ] ,
" https://base.example/dotslash/ " . to_string ( )
) ;
assert_eq! (
import_map
. imports
. get ( " https://base.example/path1/ " )
. unwrap ( ) [ 0 ] ,
" https://base.example/dotdotslash/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://base.example/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/slash/ " . to_string ( )
) ;
// Should treat percent-encoded variants of ./, ../, or / as bare specifiers..
let json_map = r #" {
" imports " : {
" %2E/ " : " /dotSlash1/ " ,
" %2E%2E/ " : " /dotDotSlash1/ " ,
" .%2F " : " /dotSlash2 " ,
" ..%2F " : " /dotDotSlash2 " ,
" %2F " : " /slash2 " ,
" %2E%2F " : " /dotSlash3 " ,
" %2E%2E%2F " : " /dotDotSlash3 "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " %2E/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/dotSlash1/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " %2E%2E/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/dotDotSlash1/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " .%2F " ) . unwrap ( ) [ 0 ] ,
" https://base.example/dotSlash2 " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " ..%2F " ) . unwrap ( ) [ 0 ] ,
" https://base.example/dotDotSlash2 " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " %2F " ) . unwrap ( ) [ 0 ] ,
" https://base.example/slash2 " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " %2E%2F " ) . unwrap ( ) [ 0 ] ,
" https://base.example/dotSlash3 " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " %2E%2E%2F " ) . unwrap ( ) [ 0 ] ,
" https://base.example/dotDotSlash3 " . to_string ( )
) ;
}
#[ test ]
fn parse_specifier_keys_absolute ( ) {
// Should only accept absolute URL specifier keys with fetch schemes,.
// treating others as bare specifiers.
let json_map = r #" {
" imports " : {
" file:///good " : " /file " ,
" http://good/ " : " /http/ " ,
" https://good/ " : " /https/ " ,
" about:bad " : " /about " ,
" blob:bad " : " /blob " ,
" data:bad " : " /data " ,
" filesystem:bad " : " /filesystem " ,
" ftp://bad/ " : " /ftp/ " ,
" import:bad " : " /import " ,
" mailto:bad " : " /mailto " ,
" javascript:bad " : " /javascript " ,
" wss:bad " : " /wss "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " http://good/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/http/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://good/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/https/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " file:///good " ) . unwrap ( ) [ 0 ] ,
" https://base.example/file " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " http://good/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/http/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " import:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/import " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " mailto:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/mailto " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " javascript:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/javascript " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " wss:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/wss " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " about:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/about " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " blob:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/blob " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " data:bad " ) . unwrap ( ) [ 0 ] ,
" https://base.example/data " . to_string ( )
) ;
// Should parse absolute URLs, treating unparseable ones as bare specifiers..
let json_map = r #" {
" imports " : {
" https://ex ample.org/ " : " /unparseable1/ " ,
" https://example.com:demo " : " /unparseable2 " ,
" http://[www.example.com]/ " : " /unparseable3/ " ,
" https:example.org " : " /invalidButParseable1/ " ,
" https://///example.com/// " : " /invalidButParseable2/ " ,
" https://example.net " : " /prettyNormal/ " ,
" https://ex%41mple.com/ " : " /percentDecoding/ " ,
" https://example.com/%41 " : " /noPercentDecoding "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " https://ex ample.org/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/unparseable1/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://example.com:demo " ) . unwrap ( ) [ 0 ] ,
" https://base.example/unparseable2 " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " http://[www.example.com]/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/unparseable3/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://example.org/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/invalidButParseable1/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://example.com/// " ) . unwrap ( ) [ 0 ] ,
" https://base.example/invalidButParseable2/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://example.net/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/prettyNormal/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://example.com/ " ) . unwrap ( ) [ 0 ] ,
" https://base.example/percentDecoding/ " . to_string ( )
) ;
assert_eq! (
import_map . imports . get ( " https://example.com/%41 " ) . unwrap ( ) [ 0 ] ,
" https://base.example/noPercentDecoding " . to_string ( )
) ;
}
#[ test ]
fn parse_scope_keys_relative ( ) {
// Should work with no prefix..
let json_map = r #" {
" scopes " : {
" foo " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/foo " ) ) ;
2019-06-09 09:08:20 -04:00
// Should work with ./, ../, and / prefixes..
let json_map = r #" {
" scopes " : {
" ./foo " : { } ,
" ../foo " : { } ,
" /foo " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/foo " ) ) ;
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/foo " ) ) ;
2019-06-09 09:08:20 -04:00
assert! ( import_map . scopes . contains_key ( " https://base.example/foo " ) ) ;
// Should work with /s, ?s, and #s..
let json_map = r #" {
" scopes " : {
" foo/bar?baz#qux " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/foo/bar?baz#qux " ) ) ;
2019-06-09 09:08:20 -04:00
// Should work with an empty string scope key..
let json_map = r #" {
" scopes " : {
" " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/path3 " ) ) ;
2019-06-09 09:08:20 -04:00
// Should work with / suffixes..
let json_map = r #" {
" scopes " : {
" foo/ " : { } ,
" ./foo/ " : { } ,
" ../foo/ " : { } ,
" /foo/ " : { } ,
" /foo// " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/foo/ " ) ) ;
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/foo/ " ) ) ;
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/foo/ " ) ) ;
2019-06-09 09:08:20 -04:00
assert! ( import_map . scopes . contains_key ( " https://base.example/foo/ " ) ) ;
assert! ( import_map . scopes . contains_key ( " https://base.example/foo// " ) ) ;
// Should deduplicate based on URL parsing rules..
let json_map = r #" {
" scopes " : {
" foo/ \\ " : { } ,
" foo// " : { } ,
" foo \\ \\ " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/foo// " ) ) ;
2019-06-09 09:08:20 -04:00
assert_eq! ( import_map . scopes . len ( ) , 1 ) ;
}
#[ test ]
fn parse_scope_keys_absolute ( ) {
// Should only accept absolute URL scope keys with fetch schemes..
let json_map = r #" {
" scopes " : {
" http://good/ " : { } ,
" https://good/ " : { } ,
" file:///good " : { } ,
" about:bad " : { } ,
" blob:bad " : { } ,
" data:bad " : { } ,
" filesystem:bad " : { } ,
" ftp://bad/ " : { } ,
" import:bad " : { } ,
" mailto:bad " : { } ,
" javascript:bad " : { } ,
" wss:bad " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert! ( import_map . scopes . contains_key ( " http://good/ " ) ) ;
assert! ( import_map . scopes . contains_key ( " https://good/ " ) ) ;
assert! ( import_map . scopes . contains_key ( " file:///good " ) ) ;
assert_eq! ( import_map . scopes . len ( ) , 3 ) ;
// Should parse absolute URL scope keys, ignoring unparseable ones..
let json_map = r #" {
" scopes " : {
" https://ex ample.org/ " : { } ,
" https://example.com:demo " : { } ,
" http://[www.example.com]/ " : { } ,
" https:example.org " : { } ,
" https://///example.com/// " : { } ,
" https://example.net " : { } ,
" https://ex%41mple.com/foo/ " : { } ,
" https://example.com/%41 " : { }
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
// tricky case! remember we have a base URL
2019-07-31 17:11:37 -04:00
assert! ( import_map
. scopes
. contains_key ( " https://base.example/path1/path2/example.org " ) ) ;
2019-06-09 09:08:20 -04:00
assert! ( import_map . scopes . contains_key ( " https://example.com/// " ) ) ;
assert! ( import_map . scopes . contains_key ( " https://example.net/ " ) ) ;
assert! ( import_map . scopes . contains_key ( " https://example.com/foo/ " ) ) ;
assert! ( import_map . scopes . contains_key ( " https://example.com/%41 " ) ) ;
assert_eq! ( import_map . scopes . len ( ) , 5 ) ;
}
#[ test ]
fn parse_addresses_relative_url_like ( ) {
// Should accept strings prefixed with ./, ../, or /..
let json_map = r #" {
" imports " : {
" dotSlash " : " ./foo " ,
" dotDotSlash " : " ../foo " ,
" slash " : " /foo "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " dotSlash " ) . unwrap ( ) ,
& vec! [ " https://base.example/path1/path2/foo " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " dotDotSlash " ) . unwrap ( ) ,
& vec! [ " https://base.example/path1/foo " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " slash " ) . unwrap ( ) ,
& vec! [ " https://base.example/foo " . to_string ( ) ]
) ;
// Should accept the literal strings ./, ../, or / with no suffix..
let json_map = r #" {
" imports " : {
" dotSlash " : " ./ " ,
" dotDotSlash " : " ../ " ,
" slash " : " / "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " dotSlash " ) . unwrap ( ) ,
& vec! [ " https://base.example/path1/path2/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " dotDotSlash " ) . unwrap ( ) ,
& vec! [ " https://base.example/path1/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " slash " ) . unwrap ( ) ,
& vec! [ " https://base.example/ " . to_string ( ) ]
) ;
// Should ignore percent-encoded variants of ./, ../, or /..
let json_map = r #" {
" imports " : {
" dotSlash1 " : " %2E/ " ,
" dotDotSlash1 " : " %2E%2E/ " ,
" dotSlash2 " : " .%2F " ,
" dotDotSlash2 " : " ..%2F " ,
" slash2 " : " %2F " ,
" dotSlash3 " : " %2E%2F " ,
" dotDotSlash3 " : " %2E%2E%2F "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert! ( import_map . imports . get ( " dotSlash1 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " dotDotSlash1 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " dotSlash2 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " dotDotSlash2 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " slash2 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " dotSlash3 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " dotDotSlash3 " ) . unwrap ( ) . is_empty ( ) ) ;
}
#[ test ]
fn parse_addresses_absolute_with_fetch_schemes ( ) {
// Should only accept absolute URL addresses with fetch schemes..
let json_map = r #" {
" imports " : {
" http " : " http://good/ " ,
" https " : " https://good/ " ,
" file " : " file:///good " ,
" about " : " about:bad " ,
" blob " : " blob:bad " ,
" data " : " data:bad " ,
" filesystem " : " filesystem:bad " ,
" ftp " : " ftp://good/ " ,
" import " : " import:bad " ,
" mailto " : " mailto:bad " ,
" javascript " : " javascript:bad " ,
" wss " : " wss:bad "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " file " ) . unwrap ( ) ,
& vec! [ " file:///good " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " http " ) . unwrap ( ) ,
& vec! [ " http://good/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " https " ) . unwrap ( ) ,
& vec! [ " https://good/ " . to_string ( ) ]
) ;
assert! ( import_map . imports . get ( " about " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " blob " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " data " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " filesystem " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " ftp " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " import " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " mailto " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " javascript " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " wss " ) . unwrap ( ) . is_empty ( ) ) ;
}
#[ test ]
fn parse_addresses_absolute_with_fetch_schemes_arrays ( ) {
// Should only accept absolute URL addresses with fetch schemes inside arrays..
let json_map = r #" {
" imports " : {
" http " : [ " http://good/ " ] ,
" https " : [ " https://good/ " ] ,
" file " : [ " file:///good " ] ,
" about " : [ " about:bad " ] ,
" blob " : [ " blob:bad " ] ,
" data " : [ " data:bad " ] ,
" filesystem " : [ " filesystem:bad " ] ,
" ftp " : [ " ftp://good/ " ] ,
" import " : [ " import:bad " ] ,
" mailto " : [ " mailto:bad " ] ,
" javascript " : [ " javascript:bad " ] ,
" wss " : [ " wss:bad " ]
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " file " ) . unwrap ( ) ,
& vec! [ " file:///good " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " http " ) . unwrap ( ) ,
& vec! [ " http://good/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " https " ) . unwrap ( ) ,
& vec! [ " https://good/ " . to_string ( ) ]
) ;
assert! ( import_map . imports . get ( " about " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " blob " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " data " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " filesystem " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " ftp " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " import " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " mailto " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " javascript " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " wss " ) . unwrap ( ) . is_empty ( ) ) ;
}
#[ test ]
fn parse_addresses_unparseable ( ) {
// Should parse absolute URLs, ignoring unparseable ones..
let json_map = r #" {
" imports " : {
" unparseable1 " : " https://ex ample.org/ " ,
" unparseable2 " : " https://example.com:demo " ,
" unparseable3 " : " http://[www.example.com]/ " ,
" invalidButParseable1 " : " https:example.org " ,
" invalidButParseable2 " : " https://///example.com/// " ,
" prettyNormal " : " https://example.net " ,
" percentDecoding " : " https://ex%41mple.com/ " ,
" noPercentDecoding " : " https://example.com/%41 "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " invalidButParseable1 " ) . unwrap ( ) ,
& vec! [ " https://example.org/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " invalidButParseable2 " ) . unwrap ( ) ,
& vec! [ " https://example.com/// " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " prettyNormal " ) . unwrap ( ) ,
& vec! [ " https://example.net/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " percentDecoding " ) . unwrap ( ) ,
& vec! [ " https://example.com/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " noPercentDecoding " ) . unwrap ( ) ,
& vec! [ " https://example.com/%41 " . to_string ( ) ]
) ;
assert! ( import_map . imports . get ( " unparseable1 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " unparseable2 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " unparseable3 " ) . unwrap ( ) . is_empty ( ) ) ;
}
#[ test ]
fn parse_addresses_unparseable_arrays ( ) {
// Should parse absolute URLs, ignoring unparseable ones inside arrays..
let json_map = r #" {
" imports " : {
" unparseable1 " : [ " https://ex ample.org/ " ] ,
" unparseable2 " : [ " https://example.com:demo " ] ,
" unparseable3 " : [ " http://[www.example.com]/ " ] ,
" invalidButParseable1 " : [ " https:example.org " ] ,
" invalidButParseable2 " : [ " https://///example.com/// " ] ,
" prettyNormal " : [ " https://example.net " ] ,
" percentDecoding " : [ " https://ex%41mple.com/ " ] ,
" noPercentDecoding " : [ " https://example.com/%41 " ]
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " invalidButParseable1 " ) . unwrap ( ) ,
& vec! [ " https://example.org/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " invalidButParseable2 " ) . unwrap ( ) ,
& vec! [ " https://example.com/// " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " prettyNormal " ) . unwrap ( ) ,
& vec! [ " https://example.net/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " percentDecoding " ) . unwrap ( ) ,
& vec! [ " https://example.com/ " . to_string ( ) ]
) ;
assert_eq! (
import_map . imports . get ( " noPercentDecoding " ) . unwrap ( ) ,
& vec! [ " https://example.com/%41 " . to_string ( ) ]
) ;
assert! ( import_map . imports . get ( " unparseable1 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " unparseable2 " ) . unwrap ( ) . is_empty ( ) ) ;
assert! ( import_map . imports . get ( " unparseable3 " ) . unwrap ( ) . is_empty ( ) ) ;
}
#[ test ]
fn parse_addresses_mismatched_trailing_slashes ( ) {
// Should parse absolute URLs, ignoring unparseable ones inside arrays..
let json_map = r #" {
" imports " : {
" trailer/ " : " /notrailer "
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert! ( import_map . imports . get ( " trailer/ " ) . unwrap ( ) . is_empty ( ) ) ;
// TODO: I'd be good to assert that warning was shown
}
#[ test ]
fn parse_addresses_mismatched_trailing_slashes_array ( ) {
// Should warn for a mismatch alone in an array..
let json_map = r #" {
" imports " : {
" trailer/ " : [ " /notrailer " ]
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert! ( import_map . imports . get ( " trailer/ " ) . unwrap ( ) . is_empty ( ) ) ;
// TODO: I'd be good to assert that warning was shown
}
#[ test ]
fn parse_addresses_mismatched_trailing_slashes_with_nonmismatched_array ( ) {
// Should warn for a mismatch alone in an array..
let json_map = r #" {
" imports " : {
" trailer/ " : [ " /atrailer/ " , " /notrailer " ]
}
} " #;
let import_map =
ImportMap ::from_json ( " https://base.example/path1/path2/path3 " , json_map )
. unwrap ( ) ;
assert_eq! (
import_map . imports . get ( " trailer/ " ) . unwrap ( ) ,
& vec! [ " https://base.example/atrailer/ " . to_string ( ) ]
) ;
// TODO: I'd be good to assert that warning was shown
}
#[ test ]
fn parse_addresses_other_invalid ( ) {
// Should ignore unprefixed strings that are not absolute URLs.
for bad in & [ " bar " , " \\ bar " , " ~bar " , " #bar " , " ?bar " ] {
let json_map = json! ( {
" imports " : {
" foo " : bad
}
} ) ;
let import_map = ImportMap ::from_json (
" https://base.example/path1/path2/path3 " ,
& json_map . to_string ( ) ,
2019-07-31 17:11:37 -04:00
)
. unwrap ( ) ;
2019-06-09 09:08:20 -04:00
assert! ( import_map . imports . get ( " foo " ) . unwrap ( ) . is_empty ( ) ) ;
}
}
fn get_empty_import_map ( ) -> ImportMap {
ImportMap {
base_url : " https://example.com/app/main.ts " . to_string ( ) ,
imports : IndexMap ::new ( ) ,
scopes : IndexMap ::new ( ) ,
}
}
2019-06-12 15:00:08 -04:00
fn assert_resolve (
result : Result < Option < ModuleSpecifier > , ImportMapError > ,
expected_url : & str ,
) {
let maybe_url = result
. unwrap_or_else ( | err | panic! ( " ImportMap::resolve failed: {:?} " , err ) ) ;
let resolved_url =
maybe_url . unwrap_or_else ( | | panic! ( " Unexpected None resolved URL " ) ) ;
assert_eq! ( resolved_url , expected_url . to_string ( ) ) ;
}
2019-06-09 09:08:20 -04:00
#[ test ]
fn resolve_unmapped_relative_specifiers ( ) {
let referrer_url = " https://example.com/js/script.ts " ;
let import_map = get_empty_import_map ( ) ;
// Should resolve ./ specifiers as URLs.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ./foo " , referrer_url ) ,
" https://example.com/js/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ./foo/bar " , referrer_url ) ,
" https://example.com/js/foo/bar " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ./foo/../bar " , referrer_url ) ,
" https://example.com/js/bar " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ./foo/../../bar " , referrer_url ) ,
" https://example.com/bar " ,
2019-06-09 09:08:20 -04:00
) ;
// Should resolve ../ specifiers as URLs.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../foo " , referrer_url ) ,
" https://example.com/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../foo/bar " , referrer_url ) ,
" https://example.com/foo/bar " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../../../foo/bar " , referrer_url ) ,
" https://example.com/foo/bar " ,
2019-06-09 09:08:20 -04:00
) ;
}
#[ test ]
fn resolve_unmapped_absolute_specifiers ( ) {
let referrer_url = " https://example.com/js/script.ts " ;
let import_map = get_empty_import_map ( ) ;
// Should resolve / specifiers as URLs.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /foo " , referrer_url ) ,
" https://example.com/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /foo/bar " , referrer_url ) ,
" https://example.com/foo/bar " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../../foo/bar " , referrer_url ) ,
" https://example.com/foo/bar " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /../foo/../bar " , referrer_url ) ,
" https://example.com/bar " ,
2019-06-09 09:08:20 -04:00
) ;
// Should parse absolute fetch-scheme URLs.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://example.net " , referrer_url ) ,
" https://example.net/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://ex%41mple.com/ " , referrer_url ) ,
" https://example.com/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https:example.org " , referrer_url ) ,
" https://example.org/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://///example.com/// " , referrer_url ) ,
" https://example.com/// " ,
2019-06-09 09:08:20 -04:00
) ;
}
#[ test ]
fn resolve_unmapped_bad_specifiers ( ) {
let referrer_url = " https://example.com/js/script.ts " ;
let import_map = get_empty_import_map ( ) ;
// Should fail for absolute non-fetch-scheme URLs.
assert! ( import_map . resolve ( " about:good " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " mailto:bad " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " import:bad " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " javascript:bad " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " wss:bad " , referrer_url ) . is_err ( ) ) ;
// Should fail for string not parseable as absolute URLs and not starting with ./, ../ or /.
assert! ( import_map . resolve ( " foo " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " \\ foo " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " :foo " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " @foo " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " %2E/foo " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " %2E%2Efoo " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " .%2Efoo " , referrer_url ) . is_err ( ) ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. resolve ( " https://ex ample.org " , referrer_url )
. is_err ( ) ) ;
assert! ( import_map
. resolve ( " https://example.org:deno " , referrer_url )
. is_err ( ) ) ;
assert! ( import_map
. resolve ( " https://[example.org] " , referrer_url )
. is_err ( ) ) ;
2019-06-09 09:08:20 -04:00
}
#[ test ]
fn resolve_imports_mapped ( ) {
let base_url = " https://example.com/app/main.ts " ;
let referrer_url = " https://example.com/js/script.ts " ;
// Should fail when mapping is to an empty array.
let json_map = r #" {
" imports " : {
" moment " : null ,
" lodash " : [ ]
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
assert! ( import_map . resolve ( " moment " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " lodash " , referrer_url ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_imports_package_like_modules ( ) {
let base_url = " https://example.com/app/main.ts " ;
let referrer_url = " https://example.com/js/script.ts " ;
let json_map = r #" {
" imports " : {
" moment " : " /deps/moment/src/moment.js " ,
" moment/ " : " /deps/moment/src/ " ,
" lodash-dot " : " ./deps/lodash-es/lodash.js " ,
" lodash-dot/ " : " ./deps/lodash-es/ " ,
" lodash-dotdot " : " ../deps/lodash-es/lodash.js " ,
" lodash-dotdot/ " : " ../deps/lodash-es/ " ,
" nowhere/ " : [ ]
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
// Should work for package main modules.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , referrer_url ) ,
" https://example.com/deps/moment/src/moment.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dot " , referrer_url ) ,
" https://example.com/app/deps/lodash-es/lodash.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dotdot " , referrer_url ) ,
" https://example.com/deps/lodash-es/lodash.js " ,
2019-06-09 09:08:20 -04:00
) ;
// Should work for package submodules.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , referrer_url ) ,
" https://example.com/deps/moment/src/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dot/foo " , referrer_url ) ,
" https://example.com/app/deps/lodash-es/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dotdot/foo " , referrer_url ) ,
" https://example.com/deps/lodash-es/foo " ,
2019-06-09 09:08:20 -04:00
) ;
// Should work for package names that end in a slash.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/ " , referrer_url ) ,
" https://example.com/deps/moment/src/ " ,
2019-06-09 09:08:20 -04:00
) ;
// Should fail for package modules that are not declared.
assert! ( import_map . resolve ( " underscore/ " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " underscore/foo " , referrer_url ) . is_err ( ) ) ;
// Should fail for package submodules that map to nowhere.
assert! ( import_map . resolve ( " nowhere/foo " , referrer_url ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_imports_tricky_specifiers ( ) {
let base_url = " https://example.com/app/main.ts " ;
let referrer_url = " https://example.com/js/script.ts " ;
let json_map = r #" {
" imports " : {
" package/withslash " : " /deps/package-with-slash/index.mjs " ,
" not-a-package " : " /lib/not-a-package.mjs " ,
" . " : " /lib/dot.mjs " ,
" .. " : " /lib/dotdot.mjs " ,
" .. \\ \\ " : " /lib/dotdotbackslash.mjs " ,
" %2E " : " /lib/percent2e.mjs " ,
" %2F " : " /lib/percent2f.mjs "
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
// Should work for explicitly-mapped specifiers that happen to have a slash.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " package/withslash " , referrer_url ) ,
" https://example.com/deps/package-with-slash/index.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should work when the specifier has punctuation.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " . " , referrer_url ) ,
" https://example.com/lib/dot.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " .. " , referrer_url ) ,
" https://example.com/lib/dotdot.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " .. \\ \\ " , referrer_url ) ,
" https://example.com/lib/dotdotbackslash.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " %2E " , referrer_url ) ,
" https://example.com/lib/percent2e.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " %2F " , referrer_url ) ,
" https://example.com/lib/percent2f.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should fail for attempting to get a submodule of something not declared with a trailing slash.
2019-07-31 17:11:37 -04:00
assert! ( import_map
. resolve ( " not-a-package/foo " , referrer_url )
. is_err ( ) ) ;
2019-06-09 09:08:20 -04:00
}
#[ test ]
fn resolve_imports_url_like_specifier ( ) {
let base_url = " https://example.com/app/main.ts " ;
let referrer_url = " https://example.com/js/script.ts " ;
let json_map = r #" {
" imports " : {
" /node_modules/als-polyfill/index.mjs " : " std:kv-storage " ,
" /lib/foo.mjs " : " ./more/bar.mjs " ,
" ./dotrelative/foo.mjs " : " /lib/dot.mjs " ,
" ../dotdotrelative/foo.mjs " : " /lib/dotdot.mjs " ,
" /lib/no.mjs " : null ,
" ./dotrelative/no.mjs " : [ ] ,
" / " : " /lib/slash-only/ " ,
" ./ " : " /lib/dotslash-only/ " ,
" /test/ " : " /lib/url-trailing-slash/ " ,
" ./test/ " : " /lib/url-trailing-slash-dot/ " ,
" /test " : " /lib/test1.mjs " ,
" ../test " : " /lib/test2.mjs "
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
// Should remap to other URLs.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://example.com/lib/foo.mjs " , referrer_url ) ,
" https://example.com/app/more/bar.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://///example.com/lib/foo.mjs " , referrer_url ) ,
" https://example.com/app/more/bar.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /lib/foo.mjs " , referrer_url ) ,
" https://example.com/app/more/bar.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
2019-06-09 09:08:20 -04:00
import_map
2019-06-12 15:00:08 -04:00
. resolve ( " https://example.com/app/dotrelative/foo.mjs " , referrer_url ) ,
" https://example.com/lib/dot.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../app/dotrelative/foo.mjs " , referrer_url ) ,
" https://example.com/lib/dot.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
2019-06-09 09:08:20 -04:00
import_map
2019-06-12 15:00:08 -04:00
. resolve ( " https://example.com/dotdotrelative/foo.mjs " , referrer_url ) ,
" https://example.com/lib/dotdot.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../dotdotrelative/foo.mjs " , referrer_url ) ,
" https://example.com/lib/dotdot.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should fail for URLs that remap to empty arrays.
2019-07-31 17:11:37 -04:00
assert! ( import_map
. resolve ( " https://example.com/lib/no.mjs " , referrer_url )
. is_err ( ) ) ;
2019-06-09 09:08:20 -04:00
assert! ( import_map . resolve ( " /lib/no.mjs " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " ../lib/no.mjs " , referrer_url ) . is_err ( ) ) ;
2019-07-31 17:11:37 -04:00
assert! ( import_map
. resolve ( " https://example.com/app/dotrelative/no.mjs " , referrer_url )
. is_err ( ) ) ;
assert! ( import_map
. resolve ( " /app/dotrelative/no.mjs " , referrer_url )
. is_err ( ) ) ;
assert! ( import_map
. resolve ( " ../app/dotrelative/no.mjs " , referrer_url )
. is_err ( ) ) ;
2019-06-09 09:08:20 -04:00
// Should remap URLs that are just composed from / and ..
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://example.com/ " , referrer_url ) ,
" https://example.com/lib/slash-only/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " / " , referrer_url ) ,
" https://example.com/lib/slash-only/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../ " , referrer_url ) ,
" https://example.com/lib/slash-only/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://example.com/app/ " , referrer_url ) ,
" https://example.com/lib/dotslash-only/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /app/ " , referrer_url ) ,
" https://example.com/lib/dotslash-only/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " ../app/ " , referrer_url ) ,
" https://example.com/lib/dotslash-only/ " ,
2019-06-09 09:08:20 -04:00
) ;
// Should remap URLs that are prefix-matched by keys with trailing slashes.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /test/foo.mjs " , referrer_url ) ,
" https://example.com/lib/url-trailing-slash/foo.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " https://example.com/app/test/foo.mjs " , referrer_url ) ,
" https://example.com/lib/url-trailing-slash-dot/foo.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should use the last entry's address when URL-like specifiers parse to the same absolute URL.
//
// NOTE: this works properly because of "preserve_order" feature flag to "serde_json" crate
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " /test " , referrer_url ) ,
" https://example.com/lib/test2.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
}
#[ test ]
fn resolve_imports_overlapping_entities_with_trailing_slashes ( ) {
let base_url = " https://example.com/app/main.ts " ;
let referrer_url = " https://example.com/js/script.ts " ;
// Should favor the most-specific key (no empty arrays).
{
let json_map = r #" {
" imports " : {
" a " : " /1 " ,
" a/ " : " /2/ " ,
" a/b " : " /3 " ,
" a/b/ " : " /4/ "
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a " , referrer_url ) ,
" https://example.com/1 " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/ " , referrer_url ) ,
" https://example.com/2/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/b " , referrer_url ) ,
" https://example.com/3 " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/b/ " , referrer_url ) ,
" https://example.com/4/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/b/c " , referrer_url ) ,
" https://example.com/4/c " ,
2019-06-09 09:08:20 -04:00
) ;
}
// Should favor the most-specific key when empty arrays are involved for less-specific keys.
{
let json_map = r #" {
" imports " : {
" a " : [ ] ,
" a/ " : [ ] ,
" a/b " : " /3 " ,
" a/b/ " : " /4/ "
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
assert! ( import_map . resolve ( " a " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " a/ " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " a/x " , referrer_url ) . is_err ( ) ) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/b " , referrer_url ) ,
" https://example.com/3 " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/b/ " , referrer_url ) ,
" https://example.com/4/ " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a/b/c " , referrer_url ) ,
" https://example.com/4/c " ,
2019-06-09 09:08:20 -04:00
) ;
assert! ( import_map . resolve ( " a/x/c " , referrer_url ) . is_err ( ) ) ;
}
}
#[ test ]
fn resolve_scopes_map_to_empty_array ( ) {
let base_url = " https://example.com/app/main.ts " ;
let referrer_url = " https://example.com/js " ;
let json_map = r #" {
" scopes " : {
" /js/ " : {
" moment " : " null " ,
" lodash " : [ ]
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
assert! ( import_map . resolve ( " moment " , referrer_url ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " lodash " , referrer_url ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_scopes_exact_vs_prefix_matching ( ) {
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" scopes " : {
" /js " : {
" moment " : " /only-triggered-by-exact/moment " ,
" moment/ " : " /only-triggered-by-exact/moment/ "
} ,
" /js/ " : {
" moment " : " /triggered-by-any-subpath/moment " ,
" moment/ " : " /triggered-by-any-subpath/moment/ "
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
let js_non_dir = " https://example.com/js " ;
let js_in_dir = " https://example.com/js/app.mjs " ;
let with_js_prefix = " https://example.com/jsiscool " ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , js_non_dir ) ,
" https://example.com/only-triggered-by-exact/moment " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , js_non_dir ) ,
" https://example.com/only-triggered-by-exact/moment/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , js_in_dir ) ,
" https://example.com/triggered-by-any-subpath/moment " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , js_in_dir ) ,
" https://example.com/triggered-by-any-subpath/moment/foo " ,
2019-06-09 09:08:20 -04:00
) ;
assert! ( import_map . resolve ( " moment " , with_js_prefix ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " moment/foo " , with_js_prefix ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_scopes_only_exact_in_map ( ) {
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" scopes " : {
" /js " : {
" moment " : " /only-triggered-by-exact/moment " ,
" moment/ " : " /only-triggered-by-exact/moment/ "
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
// Should match correctly when only an exact match is in the map.
let js_non_dir = " https://example.com/js " ;
let js_in_dir = " https://example.com/js/app.mjs " ;
let with_js_prefix = " https://example.com/jsiscool " ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , js_non_dir ) ,
" https://example.com/only-triggered-by-exact/moment " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , js_non_dir ) ,
" https://example.com/only-triggered-by-exact/moment/foo " ,
2019-06-09 09:08:20 -04:00
) ;
assert! ( import_map . resolve ( " moment " , js_in_dir ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " moment/foo " , js_in_dir ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " moment " , with_js_prefix ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " moment/foo " , with_js_prefix ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_scopes_only_prefix_in_map ( ) {
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" scopes " : {
" /js/ " : {
" moment " : " /triggered-by-any-subpath/moment " ,
" moment/ " : " /triggered-by-any-subpath/moment/ "
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
// Should match correctly when only a prefix match is in the map.
let js_non_dir = " https://example.com/js " ;
let js_in_dir = " https://example.com/js/app.mjs " ;
let with_js_prefix = " https://example.com/jsiscool " ;
assert! ( import_map . resolve ( " moment " , js_non_dir ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " moment/foo " , js_non_dir ) . is_err ( ) ) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , js_in_dir ) ,
" https://example.com/triggered-by-any-subpath/moment " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , js_in_dir ) ,
" https://example.com/triggered-by-any-subpath/moment/foo " ,
2019-06-09 09:08:20 -04:00
) ;
assert! ( import_map . resolve ( " moment " , with_js_prefix ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " moment/foo " , with_js_prefix ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_scopes_package_like ( ) {
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" imports " : {
" moment " : " /node_modules/moment/src/moment.js " ,
" moment/ " : " /node_modules/moment/src/ " ,
" lodash-dot " : " ./node_modules/lodash-es/lodash.js " ,
" lodash-dot/ " : " ./node_modules/lodash-es/ " ,
" lodash-dotdot " : " ../node_modules/lodash-es/lodash.js " ,
" lodash-dotdot/ " : " ../node_modules/lodash-es/ "
} ,
" scopes " : {
" / " : {
" moment " : " /node_modules_3/moment/src/moment.js " ,
" vue " : " /node_modules_3/vue/dist/vue.runtime.esm.js "
} ,
" /js/ " : {
" lodash-dot " : " ./node_modules_2/lodash-es/lodash.js " ,
" lodash-dot/ " : " ./node_modules_2/lodash-es/ " ,
" lodash-dotdot " : " ../node_modules_2/lodash-es/lodash.js " ,
" lodash-dotdot/ " : " ../node_modules_2/lodash-es/ "
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
// Should match correctly when only a prefix match is in the map.
let js_in_dir = " https://example.com/js/app.mjs " ;
let top_level = " https://example.com/app.mjs " ;
// Should resolve scoped.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dot " , js_in_dir ) ,
" https://example.com/app/node_modules_2/lodash-es/lodash.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dotdot " , js_in_dir ) ,
" https://example.com/node_modules_2/lodash-es/lodash.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dot/foo " , js_in_dir ) ,
" https://example.com/app/node_modules_2/lodash-es/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dotdot/foo " , js_in_dir ) ,
" https://example.com/node_modules_2/lodash-es/foo " ,
2019-06-09 09:08:20 -04:00
) ;
// Should apply best scope match.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , top_level ) ,
" https://example.com/node_modules_3/moment/src/moment.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment " , js_in_dir ) ,
" https://example.com/node_modules_3/moment/src/moment.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " vue " , js_in_dir ) ,
" https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js " ,
2019-06-09 09:08:20 -04:00
) ;
// Should fallback to "imports".
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , top_level ) ,
" https://example.com/node_modules/moment/src/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " moment/foo " , js_in_dir ) ,
" https://example.com/node_modules/moment/src/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dot " , top_level ) ,
" https://example.com/app/node_modules/lodash-es/lodash.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dotdot " , top_level ) ,
" https://example.com/node_modules/lodash-es/lodash.js " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dot/foo " , top_level ) ,
" https://example.com/app/node_modules/lodash-es/foo " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " lodash-dotdot/foo " , top_level ) ,
" https://example.com/node_modules/lodash-es/foo " ,
2019-06-09 09:08:20 -04:00
) ;
// Should still fail for package-like specifiers that are not declared.
assert! ( import_map . resolve ( " underscore/ " , js_in_dir ) . is_err ( ) ) ;
assert! ( import_map . resolve ( " underscore/foo " , js_in_dir ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_scopes_inheritance ( ) {
// https://github.com/WICG/import-maps#scope-inheritance
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" imports " : {
" a " : " /a-1.mjs " ,
" b " : " /b-1.mjs " ,
" c " : " /c-1.mjs "
} ,
" scopes " : {
" /scope2/ " : {
" a " : " /a-2.mjs "
} ,
" /scope2/scope3/ " : {
" b " : " /b-3.mjs "
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
let scope_1_url = " https://example.com/scope1/foo.mjs " ;
let scope_2_url = " https://example.com/scope2/foo.mjs " ;
let scope_3_url = " https://example.com/scope2/scope3/foo.mjs " ;
// Should fall back to "imports" when none match.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a " , scope_1_url ) ,
" https://example.com/a-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " b " , scope_1_url ) ,
" https://example.com/b-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " c " , scope_1_url ) ,
" https://example.com/c-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should use a direct scope override.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a " , scope_2_url ) ,
" https://example.com/a-2.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " b " , scope_2_url ) ,
" https://example.com/b-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " c " , scope_2_url ) ,
" https://example.com/c-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should use an indirect scope override.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a " , scope_3_url ) ,
" https://example.com/a-2.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " b " , scope_3_url ) ,
" https://example.com/b-3.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " c " , scope_3_url ) ,
" https://example.com/c-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
}
#[ test ]
fn resolve_scopes_relative_url_keys ( ) {
// https://github.com/WICG/import-maps#scope-inheritance
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" imports " : {
" a " : " /a-1.mjs " ,
" b " : " /b-1.mjs " ,
" c " : " /c-1.mjs "
} ,
" scopes " : {
" " : {
" a " : " /a-empty-string.mjs "
} ,
" ./ " : {
" b " : " /b-dot-slash.mjs "
} ,
" ../ " : {
" c " : " /c-dot-dot-slash.mjs "
}
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
let in_same_dir_as_map = " https://example.com/app/foo.mjs " ;
let in_dir_above_map = " https://example.com/foo.mjs " ;
// Should resolve an empty string scope using the import map URL.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a " , base_url ) ,
" https://example.com/a-empty-string.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " a " , in_same_dir_as_map ) ,
" https://example.com/a-1.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should resolve a ./ scope using the import map URL's directory.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " b " , base_url ) ,
" https://example.com/b-dot-slash.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " b " , in_same_dir_as_map ) ,
" https://example.com/b-dot-slash.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
// Should resolve a ../ scope using the import map URL's directory.
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " c " , base_url ) ,
" https://example.com/c-dot-dot-slash.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " c " , in_same_dir_as_map ) ,
" https://example.com/c-dot-dot-slash.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " c " , in_dir_above_map ) ,
" https://example.com/c-dot-dot-slash.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
}
#[ test ]
fn cant_resolve_to_built_in ( ) {
let base_url = " https://example.com/app/main.ts " ;
let import_map = ImportMap ::from_json ( base_url , " {} " ) . unwrap ( ) ;
assert! ( import_map . resolve ( " std:blank " , base_url ) . is_err ( ) ) ;
}
#[ test ]
fn resolve_builtins_remap ( ) {
let base_url = " https://example.com/app/main.ts " ;
let json_map = r #" {
" imports " : {
" std:blank " : " ./blank.mjs " ,
" std:none " : " ./none.mjs "
}
} " #;
let import_map = ImportMap ::from_json ( base_url , json_map ) . unwrap ( ) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " std:blank " , base_url ) ,
" https://example.com/app/blank.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
2019-06-12 15:00:08 -04:00
assert_resolve (
import_map . resolve ( " std:none " , base_url ) ,
" https://example.com/app/none.mjs " ,
2019-06-09 09:08:20 -04:00
) ;
}
}