2020-09-24 18:31:17 -04:00
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate ::ast ::parse ;
2020-10-19 23:10:42 -04:00
use crate ::ast ::transpile_module ;
use crate ::ast ::BundleHook ;
use crate ::ast ::EmitOptions ;
2020-09-24 18:31:17 -04:00
use crate ::ast ::Location ;
use crate ::ast ::ParsedModule ;
2020-10-22 20:50:15 -04:00
use crate ::colors ;
use crate ::diagnostics ::Diagnostics ;
2020-09-24 18:31:17 -04:00
use crate ::import_map ::ImportMap ;
2020-10-11 22:25:27 -04:00
use crate ::info ::ModuleGraphInfo ;
use crate ::info ::ModuleInfo ;
use crate ::info ::ModuleInfoMap ;
use crate ::info ::ModuleInfoMapItem ;
2020-10-22 20:50:15 -04:00
use crate ::js ;
2020-09-24 18:31:17 -04:00
use crate ::lockfile ::Lockfile ;
use crate ::media_type ::MediaType ;
use crate ::specifier_handler ::CachedModule ;
2020-10-22 20:50:15 -04:00
use crate ::specifier_handler ::Dependency ;
2020-09-24 18:31:17 -04:00
use crate ::specifier_handler ::DependencyMap ;
2020-10-13 06:54:28 -04:00
use crate ::specifier_handler ::Emit ;
2020-09-24 18:31:17 -04:00
use crate ::specifier_handler ::FetchFuture ;
use crate ::specifier_handler ::SpecifierHandler ;
2020-10-22 20:50:15 -04:00
use crate ::tsc2 ::exec ;
use crate ::tsc2 ::Request ;
2020-09-24 18:31:17 -04:00
use crate ::tsc_config ::IgnoredCompilerOptions ;
2020-09-29 03:16:12 -04:00
use crate ::tsc_config ::TsConfig ;
2020-09-30 07:46:42 -04:00
use crate ::version ;
2020-09-24 18:31:17 -04:00
use crate ::AnyError ;
2020-10-19 23:10:42 -04:00
use deno_core ::error ::Context ;
2020-09-24 18:31:17 -04:00
use deno_core ::futures ::stream ::FuturesUnordered ;
use deno_core ::futures ::stream ::StreamExt ;
2020-10-22 20:50:15 -04:00
use deno_core ::serde ::Serialize ;
use deno_core ::serde ::Serializer ;
2020-09-24 18:31:17 -04:00
use deno_core ::serde_json ::json ;
2020-10-22 20:50:15 -04:00
use deno_core ::ModuleResolutionError ;
2020-09-24 18:31:17 -04:00
use deno_core ::ModuleSpecifier ;
use regex ::Regex ;
use serde ::Deserialize ;
use serde ::Deserializer ;
use std ::cell ::RefCell ;
use std ::collections ::HashMap ;
use std ::collections ::HashSet ;
use std ::error ::Error ;
use std ::fmt ;
2020-10-11 22:25:27 -04:00
use std ::path ::PathBuf ;
2020-09-24 18:31:17 -04:00
use std ::rc ::Rc ;
use std ::result ;
2020-10-23 17:01:54 -04:00
use std ::sync ::Arc ;
2020-09-24 18:31:17 -04:00
use std ::sync ::Mutex ;
use std ::time ::Instant ;
lazy_static! {
/// Matched the `@deno-types` pragma.
static ref DENO_TYPES_RE : Regex =
Regex ::new ( r # "(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"# )
. unwrap ( ) ;
/// Matches a `/// <reference ... />` comment reference.
static ref TRIPLE_SLASH_REFERENCE_RE : Regex =
Regex ::new ( r "(?i)^/\s*<reference\s.*?/>" ) . unwrap ( ) ;
/// Matches a path reference, which adds a dependency to a module
static ref PATH_REFERENCE_RE : Regex =
Regex ::new ( r # "(?i)\spath\s*=\s*["']([^"']*)["']"# ) . unwrap ( ) ;
/// Matches a types reference, which for JavaScript files indicates the
/// location of types to use when type checking a program that includes it as
/// a dependency.
static ref TYPES_REFERENCE_RE : Regex =
Regex ::new ( r # "(?i)\stypes\s*=\s*["']([^"']*)["']"# ) . unwrap ( ) ;
}
/// A group of errors that represent errors that can occur when interacting with
/// a module graph.
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub enum GraphError {
/// A module using the HTTPS protocol is trying to import a module with an
/// HTTP schema.
InvalidDowngrade ( ModuleSpecifier , Location ) ,
/// A remote module is trying to import a local module.
InvalidLocalImport ( ModuleSpecifier , Location ) ,
2020-10-22 20:50:15 -04:00
/// The source code is invalid, as it does not match the expected hash in the
/// lockfile.
InvalidSource ( ModuleSpecifier , PathBuf ) ,
2020-09-24 18:31:17 -04:00
/// An unexpected dependency was requested for a module.
MissingDependency ( ModuleSpecifier , String ) ,
/// An unexpected specifier was requested.
MissingSpecifier ( ModuleSpecifier ) ,
/// The current feature is not supported.
NotSupported ( String ) ,
2020-10-22 20:50:15 -04:00
/// A unsupported media type was attempted to be imported as a module.
UnsupportedImportType ( ModuleSpecifier , MediaType ) ,
2020-09-24 18:31:17 -04:00
}
impl fmt ::Display for GraphError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
2020-10-22 20:50:15 -04:00
GraphError ::InvalidDowngrade ( ref specifier , ref location ) = > write! ( f , " Modules imported via https are not allowed to import http modules. \n Importing: {} \n at {} " , specifier , location ) ,
GraphError ::InvalidLocalImport ( ref specifier , ref location ) = > write! ( f , " Remote modules are not allowed to import local modules. Consider using a dynamic import instead. \n Importing: {} \n at {} " , specifier , location ) ,
GraphError ::InvalidSource ( ref specifier , ref lockfile ) = > write! ( f , " The source code is invalid, as it does not match the expected hash in the lock file. \n Specifier: {} \n Lock file: {} " , specifier , lockfile . to_str ( ) . unwrap ( ) ) ,
GraphError ::MissingDependency ( ref referrer , specifier ) = > write! (
2020-09-24 18:31:17 -04:00
f ,
" The graph is missing a dependency. \n Specifier: {} from {} " ,
specifier , referrer
) ,
2020-10-22 20:50:15 -04:00
GraphError ::MissingSpecifier ( ref specifier ) = > write! (
2020-09-24 18:31:17 -04:00
f ,
" The graph is missing a specifier. \n Specifier: {} " ,
specifier
) ,
2020-10-22 20:50:15 -04:00
GraphError ::NotSupported ( ref msg ) = > write! ( f , " {} " , msg ) ,
GraphError ::UnsupportedImportType ( ref specifier , ref media_type ) = > write! ( f , " An unsupported media type was attempted to be imported as a module. \n Specifier: {} \n MediaType: {} " , specifier , media_type ) ,
2020-09-24 18:31:17 -04:00
}
}
}
impl Error for GraphError { }
2020-10-19 23:10:42 -04:00
/// A structure for handling bundle loading, which is implemented here, to
/// avoid a circular dependency with `ast`.
struct BundleLoader < ' a > {
cm : Rc < swc_common ::SourceMap > ,
graph : & ' a Graph2 ,
emit_options : & ' a EmitOptions ,
}
impl < ' a > BundleLoader < ' a > {
pub fn new (
graph : & ' a Graph2 ,
emit_options : & ' a EmitOptions ,
cm : Rc < swc_common ::SourceMap > ,
) -> Self {
BundleLoader {
cm ,
graph ,
emit_options ,
}
}
}
impl swc_bundler ::Load for BundleLoader < '_ > {
fn load (
& self ,
file : & swc_common ::FileName ,
) -> Result < ( Rc < swc_common ::SourceFile > , swc_ecmascript ::ast ::Module ) , AnyError >
{
match file {
swc_common ::FileName ::Custom ( filename ) = > {
let specifier = ModuleSpecifier ::resolve_url_or_path ( filename )
. context ( " Failed to convert swc FileName to ModuleSpecifier. " ) ? ;
if let Some ( src ) = self . graph . get_source ( & specifier ) {
let media_type = self
. graph
. get_media_type ( & specifier )
. context ( " Looking up media type during bundling. " ) ? ;
transpile_module (
filename ,
& src ,
& media_type ,
self . emit_options ,
self . cm . clone ( ) ,
)
} else {
2020-10-22 20:50:15 -04:00
Err (
GraphError ::MissingDependency ( specifier , " <bundle> " . to_string ( ) )
. into ( ) ,
)
2020-10-19 23:10:42 -04:00
}
}
_ = > unreachable! ( " Received request for unsupported filename {:?} " , file ) ,
}
}
}
2020-09-24 18:31:17 -04:00
/// An enum which represents the parsed out values of references in source code.
#[ derive(Debug, Clone, Eq, PartialEq) ]
enum TypeScriptReference {
Path ( String ) ,
Types ( String ) ,
}
/// Determine if a comment contains a triple slash reference and optionally
/// return its kind and value.
fn parse_ts_reference ( comment : & str ) -> Option < TypeScriptReference > {
if ! TRIPLE_SLASH_REFERENCE_RE . is_match ( comment ) {
None
} else if let Some ( captures ) = PATH_REFERENCE_RE . captures ( comment ) {
Some ( TypeScriptReference ::Path (
captures . get ( 1 ) . unwrap ( ) . as_str ( ) . to_string ( ) ,
) )
} else if let Some ( captures ) = TYPES_REFERENCE_RE . captures ( comment ) {
Some ( TypeScriptReference ::Types (
captures . get ( 1 ) . unwrap ( ) . as_str ( ) . to_string ( ) ,
) )
} else {
None
}
}
/// Determine if a comment contains a `@deno-types` pragma and optionally return
/// its value.
fn parse_deno_types ( comment : & str ) -> Option < String > {
if let Some ( captures ) = DENO_TYPES_RE . captures ( comment ) {
if let Some ( m ) = captures . get ( 1 ) {
Some ( m . as_str ( ) . to_string ( ) )
} else if let Some ( m ) = captures . get ( 2 ) {
Some ( m . as_str ( ) . to_string ( ) )
} else {
panic! ( " unreachable " ) ;
}
} else {
None
}
}
2020-09-30 07:46:42 -04:00
/// A hashing function that takes the source code, version and optionally a
/// user provided config and generates a string hash which can be stored to
/// determine if the cached emit is valid or not.
2020-10-07 07:43:44 -04:00
fn get_version ( source : & str , version : & str , config : & [ u8 ] ) -> String {
crate ::checksum ::gen ( & [ source . as_bytes ( ) , version . as_bytes ( ) , config ] )
2020-09-30 07:46:42 -04:00
}
2020-09-24 18:31:17 -04:00
/// A logical representation of a module within a graph.
#[ derive(Debug, Clone) ]
struct Module {
dependencies : DependencyMap ,
is_dirty : bool ,
is_parsed : bool ,
2020-10-13 06:54:28 -04:00
maybe_emit : Option < Emit > ,
maybe_emit_path : Option < ( PathBuf , Option < PathBuf > ) > ,
2020-09-24 18:31:17 -04:00
maybe_import_map : Option < Rc < RefCell < ImportMap > > > ,
maybe_parsed_module : Option < ParsedModule > ,
maybe_types : Option < ( String , ModuleSpecifier ) > ,
2020-09-30 07:46:42 -04:00
maybe_version : Option < String > ,
2020-09-24 18:31:17 -04:00
media_type : MediaType ,
specifier : ModuleSpecifier ,
2020-10-07 07:43:44 -04:00
source : String ,
2020-10-11 22:25:27 -04:00
source_path : PathBuf ,
2020-09-24 18:31:17 -04:00
}
impl Default for Module {
fn default ( ) -> Self {
Module {
dependencies : HashMap ::new ( ) ,
is_dirty : false ,
is_parsed : false ,
2020-10-13 06:54:28 -04:00
maybe_emit : None ,
2020-10-11 22:25:27 -04:00
maybe_emit_path : None ,
2020-09-24 18:31:17 -04:00
maybe_import_map : None ,
maybe_parsed_module : None ,
maybe_types : None ,
2020-09-30 07:46:42 -04:00
maybe_version : None ,
2020-09-24 18:31:17 -04:00
media_type : MediaType ::Unknown ,
2020-10-15 19:34:55 -04:00
specifier : ModuleSpecifier ::resolve_url ( " file:///example.js " ) . unwrap ( ) ,
2020-10-07 07:43:44 -04:00
source : " " . to_string ( ) ,
2020-10-11 22:25:27 -04:00
source_path : PathBuf ::new ( ) ,
2020-09-24 18:31:17 -04:00
}
}
}
impl Module {
pub fn new (
2020-10-15 19:34:55 -04:00
cached_module : CachedModule ,
2020-10-22 20:50:15 -04:00
is_root : bool ,
2020-09-24 18:31:17 -04:00
maybe_import_map : Option < Rc < RefCell < ImportMap > > > ,
) -> Self {
2020-10-22 20:50:15 -04:00
// If this is a local root file, and its media type is unknown, set the
// media type to JavaScript. This allows easier ability to create "shell"
// scripts with Deno.
let media_type = if is_root
& & ! cached_module . is_remote
& & cached_module . media_type = = MediaType ::Unknown
{
MediaType ::JavaScript
} else {
cached_module . media_type
} ;
2020-10-15 19:34:55 -04:00
let mut module = Module {
specifier : cached_module . specifier ,
2020-09-24 18:31:17 -04:00
maybe_import_map ,
2020-10-22 20:50:15 -04:00
media_type ,
2020-10-15 19:34:55 -04:00
source : cached_module . source ,
source_path : cached_module . source_path ,
maybe_emit : cached_module . maybe_emit ,
maybe_emit_path : cached_module . maybe_emit_path ,
maybe_version : cached_module . maybe_version ,
is_dirty : false ,
.. Self ::default ( )
} ;
if module . maybe_import_map . is_none ( ) {
2020-09-24 18:31:17 -04:00
if let Some ( dependencies ) = cached_module . maybe_dependencies {
2020-10-15 19:34:55 -04:00
module . dependencies = dependencies ;
module . is_parsed = true ;
2020-09-24 18:31:17 -04:00
}
}
2020-10-15 19:34:55 -04:00
module . maybe_types = if let Some ( ref specifier ) = cached_module . maybe_types
{
2020-09-24 18:31:17 -04:00
Some ( (
specifier . clone ( ) ,
2020-10-15 19:34:55 -04:00
module
2020-09-24 18:31:17 -04:00
. resolve_import ( & specifier , None )
. expect ( " could not resolve module " ) ,
) )
} else {
None
} ;
2020-10-15 19:34:55 -04:00
module
}
/// Return `true` if the current hash of the module matches the stored
/// version.
pub fn is_emit_valid ( & self , config : & [ u8 ] ) -> bool {
if let Some ( version ) = self . maybe_version . clone ( ) {
version = = get_version ( & self . source , version ::DENO , config )
} else {
false
}
2020-09-24 18:31:17 -04:00
}
2020-10-22 20:50:15 -04:00
/// Parse a module, populating the structure with data retrieved from the
/// source of the module.
2020-09-29 03:16:12 -04:00
pub fn parse ( & mut self ) -> Result < ( ) , AnyError > {
2020-10-26 09:03:03 -04:00
let parsed_module =
parse ( self . specifier . as_str ( ) , & self . source , & self . media_type ) ? ;
2020-09-24 18:31:17 -04:00
// parse out any triple slash references
for comment in parsed_module . get_leading_comments ( ) . iter ( ) {
if let Some ( ts_reference ) = parse_ts_reference ( & comment . text ) {
2020-10-22 20:50:15 -04:00
let location = parsed_module . get_location ( & comment . span ) ;
2020-09-24 18:31:17 -04:00
match ts_reference {
TypeScriptReference ::Path ( import ) = > {
2020-10-22 20:50:15 -04:00
let specifier =
self . resolve_import ( & import , Some ( location . clone ( ) ) ) ? ;
let dep = self
. dependencies
. entry ( import )
. or_insert_with ( | | Dependency ::new ( location ) ) ;
2020-09-24 18:31:17 -04:00
dep . maybe_code = Some ( specifier ) ;
}
TypeScriptReference ::Types ( import ) = > {
2020-10-22 20:50:15 -04:00
let specifier =
self . resolve_import ( & import , Some ( location . clone ( ) ) ) ? ;
2020-09-24 18:31:17 -04:00
if self . media_type = = MediaType ::JavaScript
| | self . media_type = = MediaType ::JSX
{
// TODO(kitsonk) we need to specifically update the cache when
// this value changes
self . maybe_types = Some ( ( import . clone ( ) , specifier ) ) ;
} else {
2020-10-22 20:50:15 -04:00
let dep = self
. dependencies
. entry ( import )
. or_insert_with ( | | Dependency ::new ( location ) ) ;
2020-09-24 18:31:17 -04:00
dep . maybe_type = Some ( specifier ) ;
}
}
}
}
}
// Parse out all the syntactical dependencies for a module
let dependencies = parsed_module . analyze_dependencies ( ) ;
2020-10-19 23:10:42 -04:00
for desc in dependencies . iter ( ) . filter ( | desc | {
desc . kind ! = swc_ecmascript ::dep_graph ::DependencyKind ::Require
} ) {
2020-09-24 18:31:17 -04:00
let location = Location {
filename : self . specifier . to_string ( ) ,
col : desc . col ,
line : desc . line ,
} ;
2020-10-22 20:50:15 -04:00
// In situations where there is a potential issue with resolving the
// import specifier, that ends up being a module resolution error for a
// code dependency, we should not throw in the `ModuleGraph` but instead
// wait until runtime and throw there, as with dynamic imports they need
// to be catchable, which means they need to be resolved at runtime.
let maybe_specifier =
match self . resolve_import ( & desc . specifier , Some ( location . clone ( ) ) ) {
Ok ( specifier ) = > Some ( specifier ) ,
Err ( any_error ) = > {
match any_error . downcast_ref ::< ModuleResolutionError > ( ) {
Some ( ModuleResolutionError ::ImportPrefixMissing ( _ , _ ) ) = > None ,
_ = > {
return Err ( any_error ) ;
}
}
}
} ;
2020-09-24 18:31:17 -04:00
// Parse out any `@deno-types` pragmas and modify dependency
2020-10-22 20:50:15 -04:00
let maybe_type = if ! desc . leading_comments . is_empty ( ) {
2020-09-24 18:31:17 -04:00
let comment = desc . leading_comments . last ( ) . unwrap ( ) ;
if let Some ( deno_types ) = parse_deno_types ( & comment . text ) . as_ref ( ) {
2020-10-22 20:50:15 -04:00
Some ( self . resolve_import ( deno_types , Some ( location . clone ( ) ) ) ? )
2020-09-24 18:31:17 -04:00
} else {
None
}
} else {
None
} ;
let dep = self
. dependencies
. entry ( desc . specifier . to_string ( ) )
2020-10-22 20:50:15 -04:00
. or_insert_with ( | | Dependency ::new ( location ) ) ;
dep . is_dynamic = desc . is_dynamic ;
if let Some ( specifier ) = maybe_specifier {
if desc . kind = = swc_ecmascript ::dep_graph ::DependencyKind ::ExportType
| | desc . kind = = swc_ecmascript ::dep_graph ::DependencyKind ::ImportType
{
dep . maybe_type = Some ( specifier ) ;
} else {
dep . maybe_code = Some ( specifier ) ;
}
2020-09-24 18:31:17 -04:00
}
2020-10-22 20:50:15 -04:00
// If the dependency wasn't a type only dependency already, and there is
// a `@deno-types` comment, then we will set the `maybe_type` dependency.
if maybe_type . is_some ( ) & & dep . maybe_type . is_none ( ) {
dep . maybe_type = maybe_type ;
2020-09-24 18:31:17 -04:00
}
}
self . maybe_parsed_module = Some ( parsed_module ) ;
Ok ( ( ) )
}
fn resolve_import (
& self ,
specifier : & str ,
maybe_location : Option < Location > ,
2020-09-29 03:16:12 -04:00
) -> Result < ModuleSpecifier , AnyError > {
2020-09-24 18:31:17 -04:00
let maybe_resolve = if let Some ( import_map ) = self . maybe_import_map . clone ( )
{
import_map
. borrow ( )
. resolve ( specifier , self . specifier . as_str ( ) ) ?
} else {
None
} ;
let specifier = if let Some ( module_specifier ) = maybe_resolve {
module_specifier
} else {
ModuleSpecifier ::resolve_import ( specifier , self . specifier . as_str ( ) ) ?
} ;
let referrer_scheme = self . specifier . as_url ( ) . scheme ( ) ;
let specifier_scheme = specifier . as_url ( ) . scheme ( ) ;
let location = maybe_location . unwrap_or ( Location {
filename : self . specifier . to_string ( ) ,
line : 0 ,
col : 0 ,
} ) ;
// Disallow downgrades from HTTPS to HTTP
if referrer_scheme = = " https " & & specifier_scheme = = " http " {
2020-10-22 20:50:15 -04:00
return Err (
GraphError ::InvalidDowngrade ( specifier . clone ( ) , location ) . into ( ) ,
) ;
2020-09-24 18:31:17 -04:00
}
// Disallow a remote URL from trying to import a local URL
if ( referrer_scheme = = " https " | | referrer_scheme = = " http " )
& & ! ( specifier_scheme = = " https " | | specifier_scheme = = " http " )
{
2020-10-22 20:50:15 -04:00
return Err (
GraphError ::InvalidLocalImport ( specifier . clone ( ) , location ) . into ( ) ,
) ;
2020-09-24 18:31:17 -04:00
}
Ok ( specifier )
}
2020-09-30 07:46:42 -04:00
/// Calculate the hashed version of the module and update the `maybe_version`.
pub fn set_version ( & mut self , config : & [ u8 ] ) {
self . maybe_version = Some ( get_version ( & self . source , version ::DENO , config ) )
}
2020-10-11 22:25:27 -04:00
pub fn size ( & self ) -> usize {
self . source . as_bytes ( ) . len ( )
}
2020-09-24 18:31:17 -04:00
}
2020-10-13 19:52:49 -04:00
#[ derive(Clone, Debug, Eq, PartialEq) ]
pub struct Stats ( pub Vec < ( String , u128 ) > ) ;
2020-09-24 18:31:17 -04:00
impl < ' de > Deserialize < ' de > for Stats {
fn deserialize < D > ( deserializer : D ) -> result ::Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
let items : Vec < ( String , u128 ) > = Deserialize ::deserialize ( deserializer ) ? ;
Ok ( Stats ( items ) )
}
}
impl fmt ::Display for Stats {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2020-10-22 20:50:15 -04:00
writeln! ( f , " Compilation statistics: " ) ? ;
2020-09-24 18:31:17 -04:00
for ( key , value ) in self . 0. clone ( ) {
2020-10-22 20:50:15 -04:00
writeln! ( f , " {}: {} " , key , value ) ? ;
2020-09-24 18:31:17 -04:00
}
Ok ( ( ) )
}
}
2020-10-22 20:50:15 -04:00
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub enum TypeLib {
DenoWindow ,
DenoWorker ,
UnstableDenoWindow ,
UnstableDenoWorker ,
}
impl Default for TypeLib {
fn default ( ) -> Self {
TypeLib ::DenoWindow
}
}
impl Serialize for TypeLib {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
let value = match self {
TypeLib ::DenoWindow = > vec! [ " deno.window " . to_string ( ) ] ,
TypeLib ::DenoWorker = > vec! [ " deno.worker " . to_string ( ) ] ,
TypeLib ::UnstableDenoWindow = > {
vec! [ " deno.window " . to_string ( ) , " deno.unstable " . to_string ( ) ]
}
TypeLib ::UnstableDenoWorker = > {
vec! [ " deno.worker " . to_string ( ) , " deno.worker " . to_string ( ) ]
}
} ;
Serialize ::serialize ( & value , serializer )
}
}
2020-10-19 23:10:42 -04:00
#[ derive(Debug, Default) ]
pub struct BundleOptions {
pub debug : bool ,
pub maybe_config_path : Option < String > ,
}
2020-10-22 20:50:15 -04:00
#[ derive(Debug, Default) ]
pub struct CheckOptions {
/// If `true` then debug logging will be output from the isolate.
pub debug : bool ,
/// Utilise the emit from `tsc` to update the emitted code for modules.
pub emit : bool ,
/// The base type libraries that should be used when type checking.
pub lib : TypeLib ,
/// An optional string that points to a user supplied TypeScript configuration
/// file that augments the the default configuration passed to the TypeScript
/// compiler.
pub maybe_config_path : Option < String > ,
/// Ignore any previously emits and ensure that all files are emitted from
/// source.
pub reload : bool ,
}
2020-09-24 18:31:17 -04:00
/// A structure which provides options when transpiling modules.
#[ derive(Debug, Default) ]
pub struct TranspileOptions {
/// If `true` then debug logging will be output from the isolate.
pub debug : bool ,
2020-09-29 03:16:12 -04:00
/// An optional string that points to a user supplied TypeScript configuration
/// file that augments the the default configuration passed to the TypeScript
/// compiler.
pub maybe_config_path : Option < String > ,
2020-10-22 20:50:15 -04:00
/// Ignore any previously emits and ensure that all files are emitted from
/// source.
pub reload : bool ,
2020-09-24 18:31:17 -04:00
}
/// A dependency graph of modules, were the modules that have been inserted via
/// the builder will be loaded into the graph. Also provides an interface to
/// be able to manipulate and handle the graph.
2020-10-23 07:05:41 -04:00
#[ derive(Debug, Clone) ]
2020-10-07 01:24:15 -04:00
pub struct Graph2 {
2020-10-22 20:50:15 -04:00
/// A reference to the specifier handler that will retrieve and cache modules
/// for the graph.
2020-09-24 18:31:17 -04:00
handler : Rc < RefCell < dyn SpecifierHandler > > ,
2020-10-22 20:50:15 -04:00
/// Optional TypeScript build info that will be passed to `tsc` if `tsc` is
/// invoked.
maybe_tsbuildinfo : Option < String > ,
/// The modules that are part of the graph.
2020-09-24 18:31:17 -04:00
modules : HashMap < ModuleSpecifier , Module > ,
2020-10-22 20:50:15 -04:00
/// A map of redirects, where a module specifier is redirected to another
/// module specifier by the handler. All modules references should be
/// resolved internally via this, before attempting to access the module via
/// the handler, to make sure the correct modules is being dealt with.
2020-10-15 19:34:55 -04:00
redirects : HashMap < ModuleSpecifier , ModuleSpecifier > ,
2020-10-22 20:50:15 -04:00
/// The module specifiers that have been uniquely added to the graph, which
/// does not include any transient dependencies.
2020-09-24 18:31:17 -04:00
roots : Vec < ModuleSpecifier > ,
2020-10-22 20:50:15 -04:00
/// If all of the root modules are dynamically imported, then this is true.
/// This is used to ensure correct `--reload` behavior, where subsequent
/// calls to a module graph where the emit is already valid do not cause the
/// graph to re-emit.
roots_dynamic : bool ,
2020-10-23 17:01:54 -04:00
// A reference to lock file that will be used to check module integrity.
maybe_lockfile : Option < Arc < Mutex < Lockfile > > > ,
2020-09-24 18:31:17 -04:00
}
2020-10-07 01:24:15 -04:00
impl Graph2 {
2020-09-24 18:31:17 -04:00
/// Create a new instance of a graph, ready to have modules loaded it.
///
/// The argument `handler` is an instance of a structure that implements the
/// `SpecifierHandler` trait.
///
2020-10-23 17:01:54 -04:00
pub fn new (
handler : Rc < RefCell < dyn SpecifierHandler > > ,
maybe_lockfile : Option < Arc < Mutex < Lockfile > > > ,
) -> Self {
2020-10-07 01:24:15 -04:00
Graph2 {
2020-09-24 18:31:17 -04:00
handler ,
2020-10-22 20:50:15 -04:00
maybe_tsbuildinfo : None ,
2020-09-24 18:31:17 -04:00
modules : HashMap ::new ( ) ,
2020-10-15 19:34:55 -04:00
redirects : HashMap ::new ( ) ,
2020-09-24 18:31:17 -04:00
roots : Vec ::new ( ) ,
2020-10-22 20:50:15 -04:00
roots_dynamic : true ,
2020-10-23 17:01:54 -04:00
maybe_lockfile ,
2020-09-24 18:31:17 -04:00
}
}
2020-10-19 23:10:42 -04:00
/// Transform the module graph into a single JavaScript module which is
/// returned as a `String` in the result.
pub fn bundle (
& self ,
options : BundleOptions ,
) -> Result < ( String , Stats , Option < IgnoredCompilerOptions > ) , AnyError > {
if self . roots . is_empty ( ) | | self . roots . len ( ) > 1 {
2020-10-22 20:50:15 -04:00
return Err ( GraphError ::NotSupported ( format! ( " Bundling is only supported when there is a single root module in the graph. Found: {} " , self . roots . len ( ) ) ) . into ( ) ) ;
2020-10-19 23:10:42 -04:00
}
let start = Instant ::now ( ) ;
let root_specifier = self . roots [ 0 ] . clone ( ) ;
let mut ts_config = TsConfig ::new ( json! ( {
" checkJs " : false ,
" emitDecoratorMetadata " : false ,
2020-10-26 09:03:03 -04:00
" inlineSourceMap " : true ,
2020-10-19 23:10:42 -04:00
" jsx " : " react " ,
" jsxFactory " : " React.createElement " ,
" jsxFragmentFactory " : " React.Fragment " ,
} ) ) ;
let maybe_ignored_options =
2020-10-26 09:03:03 -04:00
ts_config . merge_tsconfig ( options . maybe_config_path ) ? ;
2020-10-19 23:10:42 -04:00
let emit_options : EmitOptions = ts_config . into ( ) ;
let cm = Rc ::new ( swc_common ::SourceMap ::new (
swc_common ::FilePathMapping ::empty ( ) ,
) ) ;
let loader = BundleLoader ::new ( self , & emit_options , cm . clone ( ) ) ;
let hook = Box ::new ( BundleHook ) ;
let globals = swc_common ::Globals ::new ( ) ;
let bundler = swc_bundler ::Bundler ::new (
& globals ,
cm . clone ( ) ,
loader ,
self ,
swc_bundler ::Config ::default ( ) ,
hook ,
) ;
let mut entries = HashMap ::new ( ) ;
entries . insert (
" bundle " . to_string ( ) ,
swc_common ::FileName ::Custom ( root_specifier . to_string ( ) ) ,
) ;
let output = bundler
. bundle ( entries )
. context ( " Unable to output bundle during Graph2::bundle(). " ) ? ;
let mut buf = Vec ::new ( ) ;
{
let mut emitter = swc_ecmascript ::codegen ::Emitter {
cfg : swc_ecmascript ::codegen ::Config { minify : false } ,
cm : cm . clone ( ) ,
comments : None ,
wr : Box ::new ( swc_ecmascript ::codegen ::text_writer ::JsWriter ::new (
cm , " \n " , & mut buf , None ,
) ) ,
} ;
emitter
. emit_module ( & output [ 0 ] . module )
. context ( " Unable to emit bundle during Graph2::bundle(). " ) ? ;
}
let s = String ::from_utf8 ( buf )
. context ( " Emitted bundle is an invalid utf-8 string. " ) ? ;
let stats = Stats ( vec! [
( " Files " . to_string ( ) , self . modules . len ( ) as u128 ) ,
( " Total time " . to_string ( ) , start . elapsed ( ) . as_millis ( ) ) ,
] ) ;
Ok ( ( s , stats , maybe_ignored_options ) )
}
2020-10-22 20:50:15 -04:00
/// Type check the module graph, corresponding to the options provided.
pub fn check (
self ,
options : CheckOptions ,
) -> Result < ( Stats , Diagnostics , Option < IgnoredCompilerOptions > ) , AnyError >
{
let mut config = TsConfig ::new ( json! ( {
" allowJs " : true ,
// TODO(@kitsonk) is this really needed?
" esModuleInterop " : true ,
// Enabled by default to align to transpile/swc defaults
" experimentalDecorators " : true ,
" incremental " : true ,
2020-10-23 06:38:35 -04:00
" isolatedModules " : true ,
2020-10-22 20:50:15 -04:00
" lib " : options . lib ,
" module " : " esnext " ,
" strict " : true ,
" target " : " esnext " ,
" tsBuildInfoFile " : " deno:///.tsbuildinfo " ,
} ) ) ;
if options . emit {
config . merge ( & json! ( {
// TODO(@kitsonk) consider enabling this by default
// see: https://github.com/denoland/deno/issues/7732
" emitDecoratorMetadata " : false ,
" jsx " : " react " ,
" inlineSourceMap " : true ,
" outDir " : " deno:// " ,
" removeComments " : true ,
} ) ) ;
} else {
config . merge ( & json! ( {
" noEmit " : true ,
} ) ) ;
}
let maybe_ignored_options =
2020-10-26 09:03:03 -04:00
config . merge_tsconfig ( options . maybe_config_path ) ? ;
2020-10-22 20:50:15 -04:00
// Short circuit if none of the modules require an emit, or all of the
// modules that require an emit have a valid emit. There is also an edge
// case where there are multiple imports of a dynamic module during a
// single invocation, if that is the case, even if there is a reload, we
// will simply look at if the emit is invalid, to avoid two checks for the
// same programme.
if ! self . needs_emit ( & config )
| | ( self . is_emit_valid ( & config )
& & ( ! options . reload | | self . roots_dynamic ) )
{
debug! ( " graph does not need to be checked or emitted. " ) ;
return Ok ( (
Stats ( Vec ::new ( ) ) ,
Diagnostics ( Vec ::new ( ) ) ,
maybe_ignored_options ,
) ) ;
}
// TODO(@kitsonk) not totally happy with this here, but this is the first
// point where we know we are actually going to check the program. If we
// moved it out of here, we wouldn't know until after the check has already
// happened, which isn't informative to the users.
for specifier in & self . roots {
info! ( " {} {} " , colors ::green ( " Check " ) , specifier ) ;
}
2020-10-25 16:17:58 -04:00
let root_names : Vec < ( ModuleSpecifier , MediaType ) > = self
. roots
. iter ( )
. map ( | ms | ( ms . clone ( ) , self . get_media_type ( ms ) . unwrap ( ) ) )
. collect ( ) ;
2020-10-22 20:50:15 -04:00
let maybe_tsbuildinfo = self . maybe_tsbuildinfo . clone ( ) ;
let hash_data =
vec! [ config . as_bytes ( ) , version ::DENO . as_bytes ( ) . to_owned ( ) ] ;
let graph = Rc ::new ( RefCell ::new ( self ) ) ;
let response = exec (
js ::compiler_isolate_init ( ) ,
Request {
config : config . clone ( ) ,
debug : options . debug ,
graph : graph . clone ( ) ,
hash_data ,
maybe_tsbuildinfo ,
root_names ,
} ,
) ? ;
let mut graph = graph . borrow_mut ( ) ;
graph . maybe_tsbuildinfo = response . maybe_tsbuildinfo ;
// Only process changes to the graph if there are no diagnostics and there
// were files emitted.
if response . diagnostics . 0. is_empty ( ) & & ! response . emitted_files . is_empty ( ) {
let mut codes = HashMap ::new ( ) ;
let mut maps = HashMap ::new ( ) ;
let check_js = config . get_check_js ( ) ;
for emit in & response . emitted_files {
if let Some ( specifiers ) = & emit . maybe_specifiers {
assert! ( specifiers . len ( ) = = 1 , " Unexpected specifier length " ) ;
let specifier = specifiers [ 0 ] . clone ( ) ;
// Sometimes if tsc sees a CommonJS file it will _helpfully_ output it
// to ESM, which we don't really want unless someone has enabled the
// check_js option.
if ! check_js
& & graph . get_media_type ( & specifier ) = = Some ( MediaType ::JavaScript )
{
debug! ( " skipping emit for {} " , specifier ) ;
continue ;
}
match emit . media_type {
MediaType ::JavaScript = > {
codes . insert ( specifier , emit . data . clone ( ) ) ;
}
MediaType ::SourceMap = > {
maps . insert ( specifier , emit . data . clone ( ) ) ;
}
_ = > unreachable! ( ) ,
}
}
}
let config = config . as_bytes ( ) ;
for ( specifier , code ) in codes . iter ( ) {
if let Some ( module ) = graph . get_module_mut ( specifier ) {
module . maybe_emit =
Some ( Emit ::Cli ( ( code . clone ( ) , maps . get ( specifier ) . cloned ( ) ) ) ) ;
module . set_version ( & config ) ;
module . is_dirty = true ;
} else {
return Err ( GraphError ::MissingSpecifier ( specifier . clone ( ) ) . into ( ) ) ;
}
}
}
graph . flush ( ) ? ;
Ok ( ( response . stats , response . diagnostics , maybe_ignored_options ) )
}
2020-10-24 19:27:00 -04:00
fn contains_module ( & self , specifier : & ModuleSpecifier ) -> bool {
let s = self . resolve_specifier ( specifier ) ;
self . modules . contains_key ( s )
}
2020-10-15 19:34:55 -04:00
/// Update the handler with any modules that are marked as _dirty_ and update
/// any build info if present.
fn flush ( & mut self ) -> Result < ( ) , AnyError > {
let mut handler = self . handler . borrow_mut ( ) ;
for ( _ , module ) in self . modules . iter_mut ( ) {
if module . is_dirty {
if let Some ( emit ) = & module . maybe_emit {
handler . set_cache ( & module . specifier , emit ) ? ;
}
if let Some ( version ) = & module . maybe_version {
handler . set_version ( & module . specifier , version . clone ( ) ) ? ;
}
module . is_dirty = false ;
}
}
for root_specifier in self . roots . iter ( ) {
2020-10-22 20:50:15 -04:00
if let Some ( tsbuildinfo ) = & self . maybe_tsbuildinfo {
handler . set_tsbuildinfo ( root_specifier , tsbuildinfo . to_owned ( ) ) ? ;
2020-10-15 19:34:55 -04:00
}
}
Ok ( ( ) )
}
2020-10-11 22:25:27 -04:00
fn get_info (
& self ,
specifier : & ModuleSpecifier ,
seen : & mut HashSet < ModuleSpecifier > ,
totals : & mut HashMap < ModuleSpecifier , usize > ,
) -> ModuleInfo {
let not_seen = seen . insert ( specifier . clone ( ) ) ;
2020-10-15 19:34:55 -04:00
let module = self . get_module ( specifier ) . unwrap ( ) ;
2020-10-11 22:25:27 -04:00
let mut deps = Vec ::new ( ) ;
let mut total_size = None ;
if not_seen {
let mut seen_deps = HashSet ::new ( ) ;
// TODO(@kitsonk) https://github.com/denoland/deno/issues/7927
for ( _ , dep ) in module . dependencies . iter ( ) {
// Check the runtime code dependency
if let Some ( code_dep ) = & dep . maybe_code {
if seen_deps . insert ( code_dep . clone ( ) ) {
deps . push ( self . get_info ( code_dep , seen , totals ) ) ;
}
}
}
deps . sort ( ) ;
total_size = if let Some ( total ) = totals . get ( specifier ) {
Some ( total . to_owned ( ) )
} else {
let mut total = deps
. iter ( )
. map ( | d | {
if let Some ( total_size ) = d . total_size {
total_size
} else {
0
}
} )
. sum ( ) ;
total + = module . size ( ) ;
totals . insert ( specifier . clone ( ) , total ) ;
Some ( total )
} ;
}
ModuleInfo {
deps ,
name : specifier . clone ( ) ,
size : module . size ( ) ,
total_size ,
}
}
fn get_info_map ( & self ) -> ModuleInfoMap {
let map = self
. modules
. iter ( )
. map ( | ( specifier , module ) | {
let mut deps = HashSet ::new ( ) ;
for ( _ , dep ) in module . dependencies . iter ( ) {
if let Some ( code_dep ) = & dep . maybe_code {
deps . insert ( code_dep . clone ( ) ) ;
}
if let Some ( type_dep ) = & dep . maybe_type {
deps . insert ( type_dep . clone ( ) ) ;
}
}
if let Some ( ( _ , types_dep ) ) = & module . maybe_types {
deps . insert ( types_dep . clone ( ) ) ;
}
let item = ModuleInfoMapItem {
deps : deps . into_iter ( ) . collect ( ) ,
size : module . size ( ) ,
} ;
( specifier . clone ( ) , item )
} )
. collect ( ) ;
ModuleInfoMap ::new ( map )
}
2020-10-15 19:34:55 -04:00
pub fn get_media_type (
& self ,
specifier : & ModuleSpecifier ,
) -> Option < MediaType > {
if let Some ( module ) = self . get_module ( specifier ) {
Some ( module . media_type )
} else {
None
}
}
fn get_module ( & self , specifier : & ModuleSpecifier ) -> Option < & Module > {
let s = self . resolve_specifier ( specifier ) ;
self . modules . get ( s )
}
2020-10-24 19:27:00 -04:00
/// Consume graph and return list of all module specifiers
/// contained in the graph.
pub fn get_modules ( & self ) -> Vec < ModuleSpecifier > {
self . modules . keys ( ) . map ( | s | s . to_owned ( ) ) . collect ( )
}
2020-10-15 19:34:55 -04:00
/// Get the source for a given module specifier. If the module is not part
/// of the graph, the result will be `None`.
pub fn get_source ( & self , specifier : & ModuleSpecifier ) -> Option < String > {
if let Some ( module ) = self . get_module ( specifier ) {
Some ( module . source . clone ( ) )
} else {
None
}
}
2020-10-22 20:50:15 -04:00
fn get_module_mut (
& mut self ,
specifier : & ModuleSpecifier ,
) -> Option < & mut Module > {
// this is duplicated code because `.resolve_specifier` requires an
// immutable borrow, but if `.resolve_specifier` is mut, then everything
// that calls it is is mut
let mut s = specifier ;
while let Some ( redirect ) = self . redirects . get ( s ) {
s = redirect ;
}
self . modules . get_mut ( s )
}
2020-10-11 22:25:27 -04:00
/// Return a structure which provides information about the module graph and
/// the relationship of the modules in the graph. This structure is used to
/// provide information for the `info` subcommand.
pub fn info ( & self ) -> Result < ModuleGraphInfo , AnyError > {
if self . roots . is_empty ( ) | | self . roots . len ( ) > 1 {
2020-10-22 20:50:15 -04:00
return Err ( GraphError ::NotSupported ( format! ( " Info is only supported when there is a single root module in the graph. Found: {} " , self . roots . len ( ) ) ) . into ( ) ) ;
2020-10-11 22:25:27 -04:00
}
let module = self . roots [ 0 ] . clone ( ) ;
2020-10-15 19:34:55 -04:00
let m = self . get_module ( & module ) . unwrap ( ) ;
2020-10-11 22:25:27 -04:00
let mut seen = HashSet ::new ( ) ;
let mut totals = HashMap ::new ( ) ;
let info = self . get_info ( & module , & mut seen , & mut totals ) ;
let files = self . get_info_map ( ) ;
let total_size = totals . get ( & module ) . unwrap_or ( & m . size ( ) ) . to_owned ( ) ;
2020-10-13 06:54:28 -04:00
let ( compiled , map ) =
if let Some ( ( emit_path , maybe_map_path ) ) = & m . maybe_emit_path {
( Some ( emit_path . clone ( ) ) , maybe_map_path . clone ( ) )
} else {
( None , None )
} ;
2020-10-11 22:25:27 -04:00
Ok ( ModuleGraphInfo {
2020-10-13 06:54:28 -04:00
compiled ,
2020-10-11 22:25:27 -04:00
dep_count : self . modules . len ( ) - 1 ,
file_type : m . media_type ,
files ,
info ,
local : m . source_path . clone ( ) ,
2020-10-13 06:54:28 -04:00
map ,
2020-10-11 22:25:27 -04:00
module ,
total_size ,
} )
}
2020-10-22 20:50:15 -04:00
/// Determines if all of the modules in the graph that require an emit have
/// a valid emit. Returns `true` if all the modules have a valid emit,
/// otherwise false.
fn is_emit_valid ( & self , config : & TsConfig ) -> bool {
let check_js = config . get_check_js ( ) ;
let config = config . as_bytes ( ) ;
self . modules . iter ( ) . all ( | ( _ , m ) | {
let needs_emit = match m . media_type {
MediaType ::TypeScript | MediaType ::TSX | MediaType ::JSX = > true ,
MediaType ::JavaScript = > check_js ,
_ = > false ,
} ;
if needs_emit {
m . is_emit_valid ( & config )
} else {
true
}
} )
}
2020-09-24 18:31:17 -04:00
/// Verify the subresource integrity of the graph based upon the optional
/// lockfile, updating the lockfile with any missing resources. This will
/// error if any of the resources do not match their lock status.
2020-10-23 17:01:54 -04:00
pub fn lock ( & self ) {
if let Some ( lf ) = self . maybe_lockfile . as_ref ( ) {
2020-09-24 18:31:17 -04:00
let mut lockfile = lf . lock ( ) . unwrap ( ) ;
for ( ms , module ) in self . modules . iter ( ) {
let specifier = module . specifier . to_string ( ) ;
2020-10-07 07:43:44 -04:00
let valid = lockfile . check_or_insert ( & specifier , & module . source ) ;
2020-09-24 18:31:17 -04:00
if ! valid {
2020-10-22 20:50:15 -04:00
eprintln! (
" {} " ,
GraphError ::InvalidSource ( ms . clone ( ) , lockfile . filename . clone ( ) )
2020-09-24 18:31:17 -04:00
) ;
2020-10-22 20:50:15 -04:00
std ::process ::exit ( 10 ) ;
2020-09-24 18:31:17 -04:00
}
}
}
2020-10-22 20:50:15 -04:00
}
2020-09-24 18:31:17 -04:00
2020-10-22 20:50:15 -04:00
/// Determines if any of the modules in the graph are required to be emitted.
/// This is similar to `emit_valid()` except that the actual emit isn't
/// checked to determine if it is valid.
fn needs_emit ( & self , config : & TsConfig ) -> bool {
let check_js = config . get_check_js ( ) ;
self . modules . iter ( ) . any ( | ( _ , m ) | match m . media_type {
MediaType ::TypeScript | MediaType ::TSX | MediaType ::JSX = > true ,
MediaType ::JavaScript = > check_js ,
_ = > false ,
} )
2020-09-24 18:31:17 -04:00
}
2020-10-13 19:52:49 -04:00
/// Given a string specifier and a referring module specifier, provide the
/// resulting module specifier and media type for the module that is part of
/// the graph.
2020-10-22 20:50:15 -04:00
///
/// # Arguments
///
/// * `specifier` - The string form of the module specifier that needs to be
/// resolved.
/// * `referrer` - The referring `ModuleSpecifier`.
/// * `prefer_types` - When resolving to a module specifier, determine if a
/// type dependency is preferred over a code dependency. This is set to
/// `true` when resolving module names for `tsc` as it needs the type
/// dependency over the code, while other consumers do not handle type only
/// dependencies.
2020-10-13 19:52:49 -04:00
pub fn resolve (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
2020-10-22 20:50:15 -04:00
prefer_types : bool ,
2020-10-13 19:52:49 -04:00
) -> Result < ModuleSpecifier , AnyError > {
2020-10-15 19:34:55 -04:00
if ! self . contains_module ( referrer ) {
2020-10-22 20:50:15 -04:00
return Err ( GraphError ::MissingSpecifier ( referrer . to_owned ( ) ) . into ( ) ) ;
2020-10-13 19:52:49 -04:00
}
2020-10-15 19:34:55 -04:00
let module = self . get_module ( referrer ) . unwrap ( ) ;
2020-10-13 19:52:49 -04:00
if ! module . dependencies . contains_key ( specifier ) {
return Err (
2020-10-22 20:50:15 -04:00
GraphError ::MissingDependency (
referrer . to_owned ( ) ,
specifier . to_owned ( ) ,
)
. into ( ) ,
2020-10-13 19:52:49 -04:00
) ;
}
let dependency = module . dependencies . get ( specifier ) . unwrap ( ) ;
// If there is a @deno-types pragma that impacts the dependency, then the
// maybe_type property will be set with that specifier, otherwise we use the
// specifier that point to the runtime code.
2020-10-22 20:50:15 -04:00
let resolved_specifier = if prefer_types & & dependency . maybe_type . is_some ( )
{
dependency . maybe_type . clone ( ) . unwrap ( )
} else if let Some ( code_specifier ) = dependency . maybe_code . clone ( ) {
code_specifier
} else {
return Err (
GraphError ::MissingDependency (
referrer . to_owned ( ) ,
specifier . to_owned ( ) ,
)
. into ( ) ,
) ;
} ;
2020-10-15 19:34:55 -04:00
if ! self . contains_module ( & resolved_specifier ) {
2020-10-13 19:52:49 -04:00
return Err (
2020-10-22 20:50:15 -04:00
GraphError ::MissingDependency (
referrer . to_owned ( ) ,
resolved_specifier . to_string ( ) ,
)
. into ( ) ,
2020-10-13 19:52:49 -04:00
) ;
}
2020-10-15 19:34:55 -04:00
let dep_module = self . get_module ( & resolved_specifier ) . unwrap ( ) ;
2020-10-13 19:52:49 -04:00
// In the case that there is a X-TypeScript-Types or a triple-slash types,
// then the `maybe_types` specifier will be populated and we should use that
// instead.
2020-10-22 20:50:15 -04:00
let result = if prefer_types & & dep_module . maybe_types . is_some ( ) {
let ( _ , types ) = dep_module . maybe_types . clone ( ) . unwrap ( ) ;
2020-10-13 19:52:49 -04:00
types
} else {
resolved_specifier
} ;
Ok ( result )
}
2020-10-15 19:34:55 -04:00
/// Takes a module specifier and returns the "final" specifier, accounting for
/// any redirects that may have occurred.
fn resolve_specifier < ' a > (
& ' a self ,
specifier : & ' a ModuleSpecifier ,
) -> & ' a ModuleSpecifier {
let mut s = specifier ;
let mut seen = HashSet ::new ( ) ;
seen . insert ( s . clone ( ) ) ;
while let Some ( redirect ) = self . redirects . get ( s ) {
if ! seen . insert ( redirect . clone ( ) ) {
eprintln! ( " An infinite loop of module redirections detected. \n Original specifier: {} " , specifier ) ;
break ;
}
s = redirect ;
if seen . len ( ) > 5 {
eprintln! ( " An excessive number of module redirections detected. \n Original specifier: {} " , specifier ) ;
break ;
}
}
s
}
2020-09-24 18:31:17 -04:00
/// Transpile (only transform) the graph, updating any emitted modules
/// with the specifier handler. The result contains any performance stats
/// from the compiler and optionally any user provided configuration compiler
/// options that were ignored.
///
/// # Arguments
///
2020-10-22 20:50:15 -04:00
/// * `options` - A structure of options which impact how the code is
2020-09-24 18:31:17 -04:00
/// transpiled.
///
pub fn transpile (
& mut self ,
options : TranspileOptions ,
2020-09-29 03:16:12 -04:00
) -> Result < ( Stats , Option < IgnoredCompilerOptions > ) , AnyError > {
2020-09-24 18:31:17 -04:00
let start = Instant ::now ( ) ;
2020-09-29 03:16:12 -04:00
let mut ts_config = TsConfig ::new ( json! ( {
2020-09-24 18:31:17 -04:00
" checkJs " : false ,
" emitDecoratorMetadata " : false ,
2020-10-26 09:03:03 -04:00
" inlineSourceMap " : true ,
2020-09-24 18:31:17 -04:00
" jsx " : " react " ,
" jsxFactory " : " React.createElement " ,
" jsxFragmentFactory " : " React.Fragment " ,
2020-09-29 03:16:12 -04:00
} ) ) ;
2020-09-24 18:31:17 -04:00
2020-09-29 03:16:12 -04:00
let maybe_ignored_options =
2020-10-26 09:03:03 -04:00
ts_config . merge_tsconfig ( options . maybe_config_path ) ? ;
2020-09-24 18:31:17 -04:00
2020-10-19 23:10:42 -04:00
let emit_options : EmitOptions = ts_config . clone ( ) . into ( ) ;
2020-09-24 18:31:17 -04:00
let mut emit_count : u128 = 0 ;
2020-10-22 20:50:15 -04:00
let config = ts_config . as_bytes ( ) ;
2020-09-24 18:31:17 -04:00
for ( _ , module ) in self . modules . iter_mut ( ) {
2020-09-30 07:46:42 -04:00
// TODO(kitsonk) a lot of this logic should be refactored into `Module` as
// we start to support other methods on the graph. Especially managing
// the dirty state is something the module itself should "own".
2020-09-24 18:31:17 -04:00
// if the module is a Dts file we should skip it
if module . media_type = = MediaType ::Dts {
continue ;
}
// if we don't have check_js enabled, we won't touch non TypeScript
// modules
2020-10-19 23:10:42 -04:00
if ! ( emit_options . check_js
2020-09-24 18:31:17 -04:00
| | module . media_type = = MediaType ::TSX
| | module . media_type = = MediaType ::TypeScript )
{
continue ;
}
2020-09-30 07:46:42 -04:00
// skip modules that already have a valid emit
2020-10-22 20:50:15 -04:00
if ! options . reload & & module . is_emit_valid ( & config ) {
2020-09-30 07:46:42 -04:00
continue ;
}
2020-09-24 18:31:17 -04:00
if module . maybe_parsed_module . is_none ( ) {
module . parse ( ) ? ;
}
let parsed_module = module . maybe_parsed_module . clone ( ) . unwrap ( ) ;
let emit = parsed_module . transpile ( & emit_options ) ? ;
emit_count + = 1 ;
2020-10-13 06:54:28 -04:00
module . maybe_emit = Some ( Emit ::Cli ( emit ) ) ;
2020-09-30 07:46:42 -04:00
module . set_version ( & config ) ;
2020-09-24 18:31:17 -04:00
module . is_dirty = true ;
}
2020-10-13 06:54:28 -04:00
self . flush ( ) ? ;
2020-09-24 18:31:17 -04:00
let stats = Stats ( vec! [
( " Files " . to_string ( ) , self . modules . len ( ) as u128 ) ,
( " Emitted " . to_string ( ) , emit_count ) ,
( " Total time " . to_string ( ) , start . elapsed ( ) . as_millis ( ) ) ,
] ) ;
Ok ( ( stats , maybe_ignored_options ) )
}
}
2020-10-19 23:10:42 -04:00
impl swc_bundler ::Resolve for Graph2 {
fn resolve (
& self ,
referrer : & swc_common ::FileName ,
specifier : & str ,
) -> Result < swc_common ::FileName , AnyError > {
let referrer = if let swc_common ::FileName ::Custom ( referrer ) = referrer {
ModuleSpecifier ::resolve_url_or_path ( referrer )
. context ( " Cannot resolve swc FileName to a module specifier " ) ?
} else {
unreachable! (
" An unexpected referrer was passed when bundling: {:?} " ,
referrer
)
} ;
2020-10-22 20:50:15 -04:00
let specifier = self . resolve ( specifier , & referrer , false ) ? ;
2020-10-19 23:10:42 -04:00
Ok ( swc_common ::FileName ::Custom ( specifier . to_string ( ) ) )
}
}
2020-09-24 18:31:17 -04:00
/// A structure for building a dependency graph of modules.
2020-10-07 01:24:15 -04:00
pub struct GraphBuilder2 {
2020-09-24 18:31:17 -04:00
fetched : HashSet < ModuleSpecifier > ,
2020-10-07 01:24:15 -04:00
graph : Graph2 ,
2020-09-24 18:31:17 -04:00
maybe_import_map : Option < Rc < RefCell < ImportMap > > > ,
pending : FuturesUnordered < FetchFuture > ,
}
2020-10-07 01:24:15 -04:00
impl GraphBuilder2 {
2020-09-24 18:31:17 -04:00
pub fn new (
handler : Rc < RefCell < dyn SpecifierHandler > > ,
maybe_import_map : Option < ImportMap > ,
2020-10-23 17:01:54 -04:00
maybe_lockfile : Option < Arc < Mutex < Lockfile > > > ,
2020-09-24 18:31:17 -04:00
) -> Self {
let internal_import_map = if let Some ( import_map ) = maybe_import_map {
Some ( Rc ::new ( RefCell ::new ( import_map ) ) )
} else {
None
} ;
2020-10-07 01:24:15 -04:00
GraphBuilder2 {
2020-10-23 17:01:54 -04:00
graph : Graph2 ::new ( handler , maybe_lockfile ) ,
2020-09-24 18:31:17 -04:00
fetched : HashSet ::new ( ) ,
maybe_import_map : internal_import_map ,
pending : FuturesUnordered ::new ( ) ,
}
}
2020-10-22 20:50:15 -04:00
/// Add a module into the graph based on a module specifier. The module
/// and any dependencies will be fetched from the handler. The module will
/// also be treated as a _root_ module in the graph.
pub async fn add (
& mut self ,
specifier : & ModuleSpecifier ,
is_dynamic : bool ,
) -> Result < ( ) , AnyError > {
self . fetch ( specifier , & None , is_dynamic ) ? ;
loop {
let cached_module = self . pending . next ( ) . await . unwrap ( ) ? ;
let is_root = & cached_module . specifier = = specifier ;
self . visit ( cached_module , is_root ) ? ;
if self . pending . is_empty ( ) {
break ;
}
}
if ! self . graph . roots . contains ( specifier ) {
self . graph . roots . push ( specifier . clone ( ) ) ;
self . graph . roots_dynamic = self . graph . roots_dynamic & & is_dynamic ;
if self . graph . maybe_tsbuildinfo . is_none ( ) {
let handler = self . graph . handler . borrow ( ) ;
self . graph . maybe_tsbuildinfo = handler . get_tsbuildinfo ( specifier ) ? ;
}
}
Ok ( ( ) )
}
2020-09-24 18:31:17 -04:00
/// Request a module to be fetched from the handler and queue up its future
/// to be awaited to be resolved.
2020-10-22 20:50:15 -04:00
fn fetch (
& mut self ,
specifier : & ModuleSpecifier ,
maybe_referrer : & Option < Location > ,
is_dynamic : bool ,
) -> Result < ( ) , AnyError > {
2020-09-24 18:31:17 -04:00
if self . fetched . contains ( & specifier ) {
return Ok ( ( ) ) ;
}
self . fetched . insert ( specifier . clone ( ) ) ;
2020-10-22 20:50:15 -04:00
let future = self . graph . handler . borrow_mut ( ) . fetch (
specifier . clone ( ) ,
maybe_referrer . clone ( ) ,
is_dynamic ,
) ;
2020-09-24 18:31:17 -04:00
self . pending . push ( future ) ;
Ok ( ( ) )
}
/// Visit a module that has been fetched, hydrating the module, analyzing its
/// dependencies if required, fetching those dependencies, and inserting the
/// module into the graph.
2020-10-22 20:50:15 -04:00
fn visit (
& mut self ,
cached_module : CachedModule ,
is_root : bool ,
) -> Result < ( ) , AnyError > {
2020-09-24 18:31:17 -04:00
let specifier = cached_module . specifier . clone ( ) ;
2020-10-15 19:34:55 -04:00
let requested_specifier = cached_module . requested_specifier . clone ( ) ;
2020-10-22 20:50:15 -04:00
let mut module =
Module ::new ( cached_module , is_root , self . maybe_import_map . clone ( ) ) ;
match module . media_type {
MediaType ::Json
| MediaType ::SourceMap
| MediaType ::TsBuildInfo
| MediaType ::Unknown = > {
return Err (
GraphError ::UnsupportedImportType (
module . specifier ,
module . media_type ,
)
. into ( ) ,
) ;
}
_ = > ( ) ,
}
2020-09-24 18:31:17 -04:00
if ! module . is_parsed {
let has_types = module . maybe_types . is_some ( ) ;
module . parse ( ) ? ;
if self . maybe_import_map . is_none ( ) {
let mut handler = self . graph . handler . borrow_mut ( ) ;
handler . set_deps ( & specifier , module . dependencies . clone ( ) ) ? ;
if ! has_types {
if let Some ( ( types , _ ) ) = module . maybe_types . clone ( ) {
handler . set_types ( & specifier , types ) ? ;
}
}
}
}
for ( _ , dep ) in module . dependencies . iter ( ) {
2020-10-22 20:50:15 -04:00
let maybe_referrer = Some ( dep . location . clone ( ) ) ;
2020-09-24 18:31:17 -04:00
if let Some ( specifier ) = dep . maybe_code . as_ref ( ) {
2020-10-22 20:50:15 -04:00
self . fetch ( specifier , & maybe_referrer , dep . is_dynamic ) ? ;
2020-09-24 18:31:17 -04:00
}
if let Some ( specifier ) = dep . maybe_type . as_ref ( ) {
2020-10-22 20:50:15 -04:00
self . fetch ( specifier , & maybe_referrer , dep . is_dynamic ) ? ;
2020-09-24 18:31:17 -04:00
}
}
if let Some ( ( _ , specifier ) ) = module . maybe_types . as_ref ( ) {
2020-10-22 20:50:15 -04:00
self . fetch ( specifier , & None , false ) ? ;
2020-09-24 18:31:17 -04:00
}
2020-10-15 19:34:55 -04:00
if specifier ! = requested_specifier {
self
. graph
. redirects
. insert ( requested_specifier , specifier . clone ( ) ) ;
}
2020-09-24 18:31:17 -04:00
self . graph . modules . insert ( specifier , module ) ;
Ok ( ( ) )
}
/// Move out the graph from the builder to be utilized further. An optional
/// lockfile can be provided, where if the sources in the graph do not match
2020-10-22 20:50:15 -04:00
/// the expected lockfile, an error will be logged and the process will exit.
2020-10-23 17:01:54 -04:00
pub fn get_graph ( self ) -> Graph2 {
self . graph . lock ( ) ;
2020-10-22 20:50:15 -04:00
self . graph
2020-09-24 18:31:17 -04:00
}
}
#[ cfg(test) ]
2020-10-13 19:52:49 -04:00
pub mod tests {
2020-09-24 18:31:17 -04:00
use super ::* ;
2020-10-07 01:24:15 -04:00
use deno_core ::futures ::future ;
2020-09-24 18:31:17 -04:00
use std ::env ;
2020-10-07 01:24:15 -04:00
use std ::fs ;
2020-09-24 18:31:17 -04:00
use std ::path ::PathBuf ;
use std ::sync ::Mutex ;
2020-10-07 01:24:15 -04:00
/// This is a testing mock for `SpecifierHandler` that uses a special file
/// system renaming to mock local and remote modules as well as provides
/// "spies" for the critical methods for testing purposes.
#[ derive(Debug, Default) ]
pub struct MockSpecifierHandler {
pub fixtures : PathBuf ,
2020-10-22 20:50:15 -04:00
pub maybe_tsbuildinfo : Option < String > ,
pub tsbuildinfo_calls : Vec < ( ModuleSpecifier , String ) > ,
2020-10-13 06:54:28 -04:00
pub cache_calls : Vec < ( ModuleSpecifier , Emit ) > ,
2020-10-07 01:24:15 -04:00
pub deps_calls : Vec < ( ModuleSpecifier , DependencyMap ) > ,
pub types_calls : Vec < ( ModuleSpecifier , String ) > ,
pub version_calls : Vec < ( ModuleSpecifier , String ) > ,
}
impl MockSpecifierHandler {
fn get_cache (
& self ,
specifier : ModuleSpecifier ,
) -> Result < CachedModule , AnyError > {
let specifier_text = specifier
. to_string ( )
. replace ( " :/// " , " _ " )
. replace ( " :// " , " _ " )
. replace ( " / " , " - " ) ;
2020-10-11 22:25:27 -04:00
let source_path = self . fixtures . join ( specifier_text ) ;
let media_type = match source_path . extension ( ) . unwrap ( ) . to_str ( ) . unwrap ( )
{
" ts " = > {
if source_path . to_string_lossy ( ) . ends_with ( " .d.ts " ) {
MediaType ::Dts
} else {
MediaType ::TypeScript
2020-10-07 01:24:15 -04:00
}
2020-10-11 22:25:27 -04:00
}
" tsx " = > MediaType ::TSX ,
" js " = > MediaType ::JavaScript ,
" jsx " = > MediaType ::JSX ,
_ = > MediaType ::Unknown ,
} ;
let source = fs ::read_to_string ( & source_path ) ? ;
2020-10-22 20:50:15 -04:00
let is_remote = specifier . as_url ( ) . scheme ( ) ! = " file " ;
2020-10-07 01:24:15 -04:00
Ok ( CachedModule {
source ,
2020-10-15 19:34:55 -04:00
requested_specifier : specifier . clone ( ) ,
2020-10-11 22:25:27 -04:00
source_path ,
2020-10-07 01:24:15 -04:00
specifier ,
media_type ,
2020-10-22 20:50:15 -04:00
is_remote ,
2020-10-07 01:24:15 -04:00
.. CachedModule ::default ( )
} )
}
}
impl SpecifierHandler for MockSpecifierHandler {
2020-10-22 20:50:15 -04:00
fn fetch (
& mut self ,
specifier : ModuleSpecifier ,
_maybe_referrer : Option < Location > ,
_is_dynamic : bool ,
) -> FetchFuture {
2020-10-07 01:24:15 -04:00
Box ::pin ( future ::ready ( self . get_cache ( specifier ) ) )
}
2020-10-22 20:50:15 -04:00
fn get_tsbuildinfo (
2020-10-07 01:24:15 -04:00
& self ,
2020-10-13 06:54:28 -04:00
_specifier : & ModuleSpecifier ,
2020-10-07 07:43:44 -04:00
) -> Result < Option < String > , AnyError > {
2020-10-22 20:50:15 -04:00
Ok ( self . maybe_tsbuildinfo . clone ( ) )
2020-10-07 01:24:15 -04:00
}
fn set_cache (
& mut self ,
specifier : & ModuleSpecifier ,
2020-10-13 06:54:28 -04:00
emit : & Emit ,
2020-10-07 01:24:15 -04:00
) -> Result < ( ) , AnyError > {
2020-10-13 06:54:28 -04:00
self . cache_calls . push ( ( specifier . clone ( ) , emit . clone ( ) ) ) ;
2020-10-07 01:24:15 -04:00
Ok ( ( ) )
}
fn set_types (
& mut self ,
specifier : & ModuleSpecifier ,
types : String ,
) -> Result < ( ) , AnyError > {
self . types_calls . push ( ( specifier . clone ( ) , types ) ) ;
Ok ( ( ) )
}
2020-10-22 20:50:15 -04:00
fn set_tsbuildinfo (
2020-10-07 01:24:15 -04:00
& mut self ,
specifier : & ModuleSpecifier ,
2020-10-22 20:50:15 -04:00
tsbuildinfo : String ,
2020-10-07 01:24:15 -04:00
) -> Result < ( ) , AnyError > {
2020-10-22 20:50:15 -04:00
self . maybe_tsbuildinfo = Some ( tsbuildinfo . clone ( ) ) ;
2020-10-07 01:24:15 -04:00
self
2020-10-22 20:50:15 -04:00
. tsbuildinfo_calls
. push ( ( specifier . clone ( ) , tsbuildinfo ) ) ;
2020-10-07 01:24:15 -04:00
Ok ( ( ) )
}
fn set_deps (
& mut self ,
specifier : & ModuleSpecifier ,
dependencies : DependencyMap ,
) -> Result < ( ) , AnyError > {
self . deps_calls . push ( ( specifier . clone ( ) , dependencies ) ) ;
Ok ( ( ) )
}
fn set_version (
& mut self ,
specifier : & ModuleSpecifier ,
version : String ,
) -> Result < ( ) , AnyError > {
self . version_calls . push ( ( specifier . clone ( ) , version ) ) ;
Ok ( ( ) )
}
}
2020-10-22 20:50:15 -04:00
async fn setup (
specifier : ModuleSpecifier ,
) -> ( Graph2 , Rc < RefCell < MockSpecifierHandler > > ) {
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
let handler = Rc ::new ( RefCell ::new ( MockSpecifierHandler {
fixtures ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-10-23 17:01:54 -04:00
let mut builder = GraphBuilder2 ::new ( handler . clone ( ) , None , None ) ;
2020-10-22 20:50:15 -04:00
builder
. add ( & specifier , false )
. await
. expect ( " module not inserted " ) ;
2020-10-23 17:01:54 -04:00
( builder . get_graph ( ) , handler )
2020-10-22 20:50:15 -04:00
}
2020-09-30 07:46:42 -04:00
#[ test ]
fn test_get_version ( ) {
2020-10-07 07:43:44 -04:00
let doc_a = " console.log(42); " ;
2020-09-30 07:46:42 -04:00
let version_a = get_version ( & doc_a , " 1.2.3 " , b " " ) ;
2020-10-07 07:43:44 -04:00
let doc_b = " console.log(42); " ;
2020-09-30 07:46:42 -04:00
let version_b = get_version ( & doc_b , " 1.2.3 " , b " " ) ;
assert_eq! ( version_a , version_b ) ;
let version_c = get_version ( & doc_a , " 1.2.3 " , b " options " ) ;
assert_ne! ( version_a , version_c ) ;
let version_d = get_version ( & doc_b , " 1.2.3 " , b " options " ) ;
assert_eq! ( version_c , version_d ) ;
let version_e = get_version ( & doc_a , " 1.2.4 " , b " " ) ;
assert_ne! ( version_a , version_e ) ;
let version_f = get_version ( & doc_b , " 1.2.4 " , b " " ) ;
assert_eq! ( version_e , version_f ) ;
}
#[ test ]
fn test_module_emit_valid ( ) {
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-09-30 07:46:42 -04:00
let maybe_version = Some ( get_version ( & source , version ::DENO , b " " ) ) ;
let module = Module {
source ,
maybe_version ,
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
let old_source = " console.log(43); " ;
let maybe_version = Some ( get_version ( old_source , version ::DENO , b " " ) ) ;
2020-09-30 07:46:42 -04:00
let module = Module {
source ,
maybe_version ,
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( ! module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-09-30 07:46:42 -04:00
let maybe_version = Some ( get_version ( & source , " 0.0.0 " , b " " ) ) ;
let module = Module {
source ,
maybe_version ,
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( ! module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-09-30 07:46:42 -04:00
let module = Module {
source ,
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( ! module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
}
#[ test ]
fn test_module_set_version ( ) {
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-09-30 07:46:42 -04:00
let expected = Some ( get_version ( & source , version ::DENO , b " " ) ) ;
let mut module = Module {
source ,
.. Module ::default ( )
} ;
assert! ( module . maybe_version . is_none ( ) ) ;
module . set_version ( b " " ) ;
assert_eq! ( module . maybe_version , expected ) ;
}
2020-10-19 23:10:42 -04:00
#[ tokio::test ]
async fn test_graph_bundle ( ) {
let tests = vec! [
( " file:///tests/fixture01.ts " , " fixture01.out " ) ,
( " file:///tests/fixture02.ts " , " fixture02.out " ) ,
( " file:///tests/fixture03.ts " , " fixture03.out " ) ,
( " file:///tests/fixture04.ts " , " fixture04.out " ) ,
( " file:///tests/fixture05.ts " , " fixture05.out " ) ,
( " file:///tests/fixture06.ts " , " fixture06.out " ) ,
( " file:///tests/fixture07.ts " , " fixture07.out " ) ,
( " file:///tests/fixture08.ts " , " fixture08.out " ) ,
( " file:///tests/fixture09.ts " , " fixture09.out " ) ,
( " file:///tests/fixture10.ts " , " fixture10.out " ) ,
( " file:///tests/fixture11.ts " , " fixture11.out " ) ,
( " file:///tests/fixture12.ts " , " fixture12.out " ) ,
( " file:///tests/fixture13.ts " , " fixture13.out " ) ,
( " file:///tests/fixture14.ts " , " fixture14.out " ) ,
] ;
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/bundle " ) ;
for ( specifier , expected_str ) in tests {
let specifier = ModuleSpecifier ::resolve_url_or_path ( specifier ) . unwrap ( ) ;
let handler = Rc ::new ( RefCell ::new ( MockSpecifierHandler {
fixtures : fixtures . clone ( ) ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-10-23 17:01:54 -04:00
let mut builder = GraphBuilder2 ::new ( handler . clone ( ) , None , None ) ;
2020-10-19 23:10:42 -04:00
builder
2020-10-22 20:50:15 -04:00
. add ( & specifier , false )
2020-10-19 23:10:42 -04:00
. await
. expect ( " module not inserted " ) ;
2020-10-23 17:01:54 -04:00
let graph = builder . get_graph ( ) ;
2020-10-19 23:10:42 -04:00
let ( actual , stats , maybe_ignored_options ) = graph
. bundle ( BundleOptions ::default ( ) )
. expect ( " could not bundle " ) ;
assert_eq! ( stats . 0. len ( ) , 2 ) ;
assert_eq! ( maybe_ignored_options , None ) ;
let expected_path = fixtures . join ( expected_str ) ;
let expected = fs ::read_to_string ( expected_path ) . unwrap ( ) ;
assert_eq! ( actual , expected , " fixture: {} " , specifier ) ;
}
}
2020-10-22 20:50:15 -04:00
#[ tokio::test ]
async fn test_graph_check_emit ( ) {
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
let ( graph , handler ) = setup ( specifier ) . await ;
let ( stats , diagnostics , maybe_ignored_options ) = graph
. check ( CheckOptions {
debug : false ,
emit : true ,
lib : TypeLib ::DenoWindow ,
maybe_config_path : None ,
reload : false ,
} )
. expect ( " should have checked " ) ;
assert! ( maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( stats . 0. len ( ) , 12 ) ;
assert! ( diagnostics . 0. is_empty ( ) ) ;
let h = handler . borrow ( ) ;
assert_eq! ( h . cache_calls . len ( ) , 2 ) ;
assert_eq! ( h . tsbuildinfo_calls . len ( ) , 1 ) ;
}
#[ tokio::test ]
async fn test_graph_check_no_emit ( ) {
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
let ( graph , handler ) = setup ( specifier ) . await ;
let ( stats , diagnostics , maybe_ignored_options ) = graph
. check ( CheckOptions {
debug : false ,
emit : false ,
lib : TypeLib ::DenoWindow ,
maybe_config_path : None ,
reload : false ,
} )
. expect ( " should have checked " ) ;
assert! ( maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( stats . 0. len ( ) , 12 ) ;
assert! ( diagnostics . 0. is_empty ( ) ) ;
let h = handler . borrow ( ) ;
assert_eq! ( h . cache_calls . len ( ) , 0 ) ;
assert_eq! ( h . tsbuildinfo_calls . len ( ) , 1 ) ;
}
2020-10-11 22:25:27 -04:00
#[ tokio::test ]
async fn test_graph_info ( ) {
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( graph , _ ) = setup ( specifier ) . await ;
2020-10-11 22:25:27 -04:00
let info = graph . info ( ) . expect ( " could not get info " ) ;
assert! ( info . compiled . is_none ( ) ) ;
assert_eq! ( info . dep_count , 6 ) ;
assert_eq! ( info . file_type , MediaType ::TypeScript ) ;
assert_eq! ( info . files . 0. len ( ) , 7 ) ;
assert! ( info . local . to_string_lossy ( ) . ends_with ( " file_tests-main.ts " ) ) ;
assert! ( info . map . is_none ( ) ) ;
assert_eq! (
info . module ,
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " ) . unwrap ( )
) ;
assert_eq! ( info . total_size , 344 ) ;
}
2020-10-22 20:50:15 -04:00
#[ tokio::test ]
async fn test_graph_import_json ( ) {
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/importjson.ts " )
. expect ( " could not resolve module " ) ;
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
let handler = Rc ::new ( RefCell ::new ( MockSpecifierHandler {
fixtures ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-10-23 17:01:54 -04:00
let mut builder = GraphBuilder2 ::new ( handler . clone ( ) , None , None ) ;
2020-10-22 20:50:15 -04:00
builder
. add ( & specifier , false )
. await
. expect_err ( " should have errored " ) ;
}
2020-09-24 18:31:17 -04:00
#[ tokio::test ]
async fn test_graph_transpile ( ) {
// This is a complex scenario of transpiling, where we have TypeScript
// importing a JavaScript file (with type definitions) which imports
// TypeScript, JavaScript, and JavaScript with type definitions.
// For scenarios where we transpile, we only want the TypeScript files
// to be actually emitted.
//
// This also exercises "@deno-types" and type references.
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( mut graph , handler ) = setup ( specifier ) . await ;
2020-09-24 18:31:17 -04:00
let ( stats , maybe_ignored_options ) =
graph . transpile ( TranspileOptions ::default ( ) ) . unwrap ( ) ;
assert_eq! ( stats . 0. len ( ) , 3 ) ;
assert_eq! ( maybe_ignored_options , None ) ;
let h = handler . borrow ( ) ;
assert_eq! ( h . cache_calls . len ( ) , 2 ) ;
2020-10-13 06:54:28 -04:00
match & h . cache_calls [ 0 ] . 1 {
Emit ::Cli ( ( code , maybe_map ) ) = > {
assert! (
code . contains ( " # sourceMappingURL=data:application/json;base64, " )
) ;
assert! ( maybe_map . is_none ( ) ) ;
}
} ;
match & h . cache_calls [ 1 ] . 1 {
Emit ::Cli ( ( code , maybe_map ) ) = > {
assert! (
code . contains ( " # sourceMappingURL=data:application/json;base64, " )
) ;
assert! ( maybe_map . is_none ( ) ) ;
}
} ;
2020-09-24 18:31:17 -04:00
assert_eq! ( h . deps_calls . len ( ) , 7 ) ;
assert_eq! (
h . deps_calls [ 0 ] . 0 ,
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " ) . unwrap ( )
) ;
assert_eq! ( h . deps_calls [ 0 ] . 1. len ( ) , 1 ) ;
assert_eq! (
h . deps_calls [ 1 ] . 0 ,
ModuleSpecifier ::resolve_url_or_path ( " https://deno.land/x/lib/mod.js " )
. unwrap ( )
) ;
assert_eq! ( h . deps_calls [ 1 ] . 1. len ( ) , 3 ) ;
assert_eq! (
h . deps_calls [ 2 ] . 0 ,
ModuleSpecifier ::resolve_url_or_path ( " https://deno.land/x/lib/mod.d.ts " )
. unwrap ( )
) ;
assert_eq! ( h . deps_calls [ 2 ] . 1. len ( ) , 3 , " should have 3 dependencies " ) ;
// sometimes the calls are not deterministic, and so checking the contents
// can cause some failures
assert_eq! ( h . deps_calls [ 3 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
assert_eq! ( h . deps_calls [ 4 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
assert_eq! ( h . deps_calls [ 5 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
assert_eq! ( h . deps_calls [ 6 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
}
#[ tokio::test ]
async fn test_graph_transpile_user_config ( ) {
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " https://deno.land/x/transpile.tsx " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( mut graph , handler ) = setup ( specifier ) . await ;
2020-09-24 18:31:17 -04:00
let ( _ , maybe_ignored_options ) = graph
. transpile ( TranspileOptions {
debug : false ,
2020-09-29 03:16:12 -04:00
maybe_config_path : Some ( " tests/module_graph/tsconfig.json " . to_string ( ) ) ,
2020-10-22 20:50:15 -04:00
reload : false ,
2020-09-24 18:31:17 -04:00
} )
. unwrap ( ) ;
assert_eq! (
2020-09-29 03:16:12 -04:00
maybe_ignored_options . unwrap ( ) . items ,
vec! [ " target " . to_string ( ) ] ,
2020-09-24 18:31:17 -04:00
" the 'target' options should have been ignored "
) ;
let h = handler . borrow ( ) ;
assert_eq! ( h . cache_calls . len ( ) , 1 , " only one file should be emitted " ) ;
2020-10-02 07:51:37 -04:00
// FIXME(bartlomieju): had to add space in `<div>`, probably a quirk in swc_ecma_codegen
2020-10-13 06:54:28 -04:00
match & h . cache_calls [ 0 ] . 1 {
Emit ::Cli ( ( code , _ ) ) = > {
assert! (
code . contains ( " <div >Hello world!</div> " ) ,
" jsx should have been preserved "
) ;
}
}
2020-09-24 18:31:17 -04:00
}
#[ tokio::test ]
async fn test_graph_with_lockfile ( ) {
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
let lockfile_path = fixtures . join ( " lockfile.json " ) ;
let lockfile =
2020-10-19 15:19:20 -04:00
Lockfile ::new ( lockfile_path , false ) . expect ( " could not load lockfile " ) ;
2020-10-23 17:01:54 -04:00
let maybe_lockfile = Some ( Arc ::new ( Mutex ::new ( lockfile ) ) ) ;
2020-09-24 18:31:17 -04:00
let handler = Rc ::new ( RefCell ::new ( MockSpecifierHandler {
fixtures ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-10-23 17:01:54 -04:00
let mut builder = GraphBuilder2 ::new ( handler . clone ( ) , None , maybe_lockfile ) ;
2020-09-24 18:31:17 -04:00
let specifier =
ModuleSpecifier ::resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
builder
2020-10-22 20:50:15 -04:00
. add ( & specifier , false )
2020-09-24 18:31:17 -04:00
. await
. expect ( " module not inserted " ) ;
2020-10-23 17:01:54 -04:00
builder . get_graph ( ) ;
2020-09-24 18:31:17 -04:00
}
}