deno/cli/program_state.rs
Nayeem Rahman 070d99645f
chore: Rename --importmap to --import-map (#7032)
--importmap still works as an alias to --import-map
but is not visible in CLI help output.
2020-10-20 14:30:59 +02:00

540 lines
16 KiB
Rust

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::deno_dir;
use crate::file_fetcher::SourceFileFetcher;
use crate::flags;
use crate::http_cache;
use crate::import_map::ImportMap;
use crate::inspector::InspectorServer;
use crate::lockfile::Lockfile;
use crate::media_type::MediaType;
use crate::module_graph::ModuleGraphFile;
use crate::module_graph::ModuleGraphLoader;
use crate::module_graph2::GraphBuilder2;
use crate::module_graph2::TranspileOptions;
use crate::permissions::Permissions;
use crate::specifier_handler::FetchHandler;
use crate::tsc::CompiledModule;
use crate::tsc::TargetLib;
use crate::tsc::TsCompiler;
use deno_core::error::AnyError;
use deno_core::ModuleSpecifier;
use std::cell::RefCell;
use std::env;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
pub fn exit_unstable(api_name: &str) {
eprintln!(
"Unstable API '{}'. The --unstable flag must be provided.",
api_name
);
std::process::exit(70);
}
/// This structure represents state of single "deno" program.
///
/// It is shared by all created workers (thus V8 isolates).
pub struct ProgramState {
/// Flags parsed from `argv` contents.
pub flags: flags::Flags,
/// Permissions parsed from `flags`.
pub permissions: Permissions,
pub dir: deno_dir::DenoDir,
pub file_fetcher: SourceFileFetcher,
pub ts_compiler: TsCompiler,
pub lockfile: Option<Mutex<Lockfile>>,
pub maybe_import_map: Option<ImportMap>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
}
impl ProgramState {
pub fn new(flags: flags::Flags) -> Result<Arc<Self>, AnyError> {
let custom_root = env::var("DENO_DIR").map(String::into).ok();
let dir = deno_dir::DenoDir::new(custom_root)?;
let deps_cache_location = dir.root.join("deps");
let http_cache = http_cache::HttpCache::new(&deps_cache_location);
let ca_file = flags.ca_file.clone().or_else(|| env::var("DENO_CERT").ok());
let file_fetcher = SourceFileFetcher::new(
http_cache,
!flags.reload,
flags.cache_blocklist.clone(),
flags.no_remote,
flags.cached_only,
ca_file.as_deref(),
)?;
let ts_compiler = TsCompiler::new(
file_fetcher.clone(),
flags.clone(),
dir.gen_cache.clone(),
)?;
let lockfile = if let Some(filename) = &flags.lock {
let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
Some(Mutex::new(lockfile))
} else {
None
};
let maybe_import_map: Option<ImportMap> =
match flags.import_map_path.as_ref() {
None => None,
Some(file_path) => {
if !flags.unstable {
exit_unstable("--import-map")
}
Some(ImportMap::load(file_path)?)
}
};
let maybe_inspect_host = flags.inspect.or(flags.inspect_brk);
let maybe_inspector_server = match maybe_inspect_host {
Some(host) => Some(Arc::new(InspectorServer::new(host))),
None => None,
};
let program_state = ProgramState {
dir,
permissions: Permissions::from_flags(&flags),
flags,
file_fetcher,
ts_compiler,
lockfile,
maybe_import_map,
maybe_inspector_server,
};
Ok(Arc::new(program_state))
}
/// This function is called when new module load is
/// initialized by the JsRuntime. Its resposibility is to collect
/// all dependencies and if it is required then also perform TS typecheck
/// and traspilation.
pub async fn prepare_module_load(
self: &Arc<Self>,
module_specifier: ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
target_lib: TargetLib,
permissions: Permissions,
is_dyn_import: bool,
maybe_import_map: Option<ImportMap>,
) -> Result<(), AnyError> {
let module_specifier = module_specifier.clone();
if self.flags.no_check {
debug!("Transpiling root: {}", module_specifier);
// TODO(kitsonk) note that self.permissions != permissions, which is
// something that should be handled better in the future.
let handler =
Rc::new(RefCell::new(FetchHandler::new(self, permissions.clone())?));
let mut builder = GraphBuilder2::new(handler, maybe_import_map);
builder.insert(&module_specifier).await?;
let mut graph = builder.get_graph(&self.lockfile)?;
let (stats, maybe_ignored_options) =
graph.transpile(TranspileOptions {
debug: self.flags.log_level == Some(log::Level::Debug),
maybe_config_path: self.flags.config_path.clone(),
})?;
if let Some(ignored_options) = maybe_ignored_options {
eprintln!("{}", ignored_options);
}
debug!("{}", stats);
} else {
let mut module_graph_loader = ModuleGraphLoader::new(
self.file_fetcher.clone(),
maybe_import_map,
permissions.clone(),
is_dyn_import,
false,
);
module_graph_loader
.add_to_graph(&module_specifier, maybe_referrer)
.await?;
let module_graph = module_graph_loader.get_graph();
let out = self
.file_fetcher
.fetch_cached_source_file(&module_specifier, permissions.clone())
.expect("Source file not found");
let module_graph_files = module_graph.values().collect::<Vec<_>>();
// Check integrity of every file in module graph
if let Some(ref lockfile) = self.lockfile {
let mut g = lockfile.lock().unwrap();
for graph_file in &module_graph_files {
let check_passed =
g.check_or_insert(&graph_file.url, &graph_file.source_code);
if !check_passed {
eprintln!(
"Subresource integrity check failed --lock={}\n{}",
g.filename.display(),
graph_file.url
);
std::process::exit(10);
}
}
}
// Check if we need to compile files.
let should_compile = needs_compilation(
self.ts_compiler.compile_js,
out.media_type,
&module_graph_files,
);
let allow_js = should_allow_js(&module_graph_files);
if should_compile {
self
.ts_compiler
.compile(self, &out, target_lib, &module_graph, allow_js)
.await?;
}
}
if let Some(ref lockfile) = self.lockfile {
let g = lockfile.lock().unwrap();
g.write()?;
}
Ok(())
}
// TODO(bartlomieju): this method doesn't need to be async anymore
/// This method is used after `prepare_module_load` finishes and JsRuntime
/// starts loading source and executing source code. This method shouldn't
/// perform any IO (besides $DENO_DIR) and only operate on sources collected
/// during `prepare_module_load`.
pub async fn fetch_compiled_module(
&self,
module_specifier: ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
) -> Result<CompiledModule, AnyError> {
let out = self
.file_fetcher
.fetch_cached_source_file(&module_specifier, Permissions::allow_all())
.expect("Cached source file doesn't exist");
// Check if we need to compile files
let was_compiled = match out.media_type {
MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true,
MediaType::JavaScript => self.ts_compiler.compile_js,
_ => false,
};
let compiled_module = if was_compiled {
match self.ts_compiler.get_compiled_module(&out.url) {
Ok(module) => module,
Err(e) => {
let msg = format!(
"Failed to get compiled source code of \"{}\".\nReason: {}\n\
If the source file provides only type exports, prefer to use \"import type\" or \"export type\" syntax instead.",
out.url, e.to_string()
);
info!("{} {}", crate::colors::yellow("Warning"), msg);
CompiledModule {
code: "".to_string(),
name: out.url.to_string(),
}
}
}
} else {
CompiledModule {
code: out.source_code,
name: out.url.to_string(),
}
};
Ok(compiled_module)
}
/// Quits the process if the --unstable flag was not provided.
///
/// This is intentionally a non-recoverable check so that people cannot probe
/// for unstable APIs from stable programs.
pub fn check_unstable(&self, api_name: &str) {
if !self.flags.unstable {
exit_unstable(api_name);
}
}
#[cfg(test)]
pub fn mock(
argv: Vec<String>,
maybe_flags: Option<flags::Flags>,
) -> Arc<ProgramState> {
ProgramState::new(flags::Flags {
argv,
..maybe_flags.unwrap_or_default()
})
.unwrap()
}
}
/// Determine if TS compiler should be run with `allowJs` setting on. This
/// is the case when there's either:
/// - a JavaScript file with non-JavaScript import
/// - JSX import
fn should_allow_js(module_graph_files: &[&ModuleGraphFile]) -> bool {
module_graph_files.iter().any(|module_file| {
if module_file.media_type == MediaType::JSX {
true
} else if module_file.media_type == MediaType::JavaScript {
module_file.imports.iter().any(|import_desc| {
let import_file = module_graph_files
.iter()
.find(|f| {
f.specifier == import_desc.resolved_specifier.to_string().as_str()
})
.expect("Failed to find imported file");
let media_type = import_file.media_type;
media_type == MediaType::TypeScript
|| media_type == MediaType::TSX
|| media_type == MediaType::JSX
})
} else {
false
}
})
}
// Compilation happens if either:
// - `checkJs` is set to true in TS config
// - entry point is a TS file
// - any dependency in module graph is a TS file
fn needs_compilation(
compile_js: bool,
media_type: MediaType,
module_graph_files: &[&ModuleGraphFile],
) -> bool {
let mut needs_compilation = match media_type {
MediaType::TypeScript | MediaType::TSX | MediaType::JSX => true,
MediaType::JavaScript => compile_js,
_ => false,
};
needs_compilation |= module_graph_files.iter().any(|module_file| {
let media_type = module_file.media_type;
media_type == (MediaType::TypeScript)
|| media_type == (MediaType::TSX)
|| media_type == (MediaType::JSX)
});
needs_compilation
}
#[test]
fn thread_safe() {
fn f<S: Send + Sync>(_: S) {}
f(ProgramState::mock(vec![], None));
}
#[test]
fn test_should_allow_js() {
use crate::ast::Location;
use crate::module_graph::ImportDescriptor;
assert!(should_allow_js(&[
&ModuleGraphFile {
specifier: "file:///some/file.ts".to_string(),
url: "file:///some/file.ts".to_string(),
redirect: None,
filename: "some/file.ts".to_string(),
imports: vec![],
version_hash: "1".to_string(),
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
media_type: MediaType::TypeScript,
source_code: "function foo() {}".to_string(),
},
&ModuleGraphFile {
specifier: "file:///some/file1.js".to_string(),
url: "file:///some/file1.js".to_string(),
redirect: None,
filename: "some/file1.js".to_string(),
version_hash: "1".to_string(),
imports: vec![ImportDescriptor {
specifier: "./file.ts".to_string(),
resolved_specifier: ModuleSpecifier::resolve_url(
"file:///some/file.ts",
)
.unwrap(),
type_directive: None,
resolved_type_directive: None,
location: Location {
filename: "file:///some/file1.js".to_string(),
line: 0,
col: 0,
},
}],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
media_type: MediaType::JavaScript,
source_code: "function foo() {}".to_string(),
},
],));
assert!(should_allow_js(&[
&ModuleGraphFile {
specifier: "file:///some/file.jsx".to_string(),
url: "file:///some/file.jsx".to_string(),
redirect: None,
filename: "some/file.jsx".to_string(),
imports: vec![],
version_hash: "1".to_string(),
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
media_type: MediaType::JSX,
source_code: "function foo() {}".to_string(),
},
&ModuleGraphFile {
specifier: "file:///some/file.ts".to_string(),
url: "file:///some/file.ts".to_string(),
redirect: None,
filename: "some/file.ts".to_string(),
version_hash: "1".to_string(),
imports: vec![ImportDescriptor {
specifier: "./file.jsx".to_string(),
resolved_specifier: ModuleSpecifier::resolve_url(
"file:///some/file.jsx",
)
.unwrap(),
type_directive: None,
resolved_type_directive: None,
location: Location {
filename: "file:///some/file1.ts".to_string(),
line: 0,
col: 0,
},
}],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
media_type: MediaType::TypeScript,
source_code: "function foo() {}".to_string(),
},
]));
assert!(!should_allow_js(&[
&ModuleGraphFile {
specifier: "file:///some/file.js".to_string(),
url: "file:///some/file.js".to_string(),
redirect: None,
filename: "some/file.js".to_string(),
imports: vec![],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
version_hash: "1".to_string(),
type_headers: vec![],
media_type: MediaType::JavaScript,
source_code: "function foo() {}".to_string(),
},
&ModuleGraphFile {
specifier: "file:///some/file1.js".to_string(),
url: "file:///some/file1.js".to_string(),
redirect: None,
filename: "some/file1.js".to_string(),
imports: vec![ImportDescriptor {
specifier: "./file.js".to_string(),
resolved_specifier: ModuleSpecifier::resolve_url(
"file:///some/file.js",
)
.unwrap(),
type_directive: None,
resolved_type_directive: None,
location: Location {
filename: "file:///some/file.js".to_string(),
line: 0,
col: 0,
},
}],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
version_hash: "1".to_string(),
type_headers: vec![],
media_type: MediaType::JavaScript,
source_code: "function foo() {}".to_string(),
},
],));
}
#[test]
fn test_needs_compilation() {
assert!(!needs_compilation(
false,
MediaType::JavaScript,
&[&ModuleGraphFile {
specifier: "some/file.js".to_string(),
url: "file:///some/file.js".to_string(),
redirect: None,
filename: "some/file.js".to_string(),
imports: vec![],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
version_hash: "1".to_string(),
media_type: MediaType::JavaScript,
source_code: "function foo() {}".to_string(),
}],
));
assert!(!needs_compilation(false, MediaType::JavaScript, &[]));
assert!(needs_compilation(true, MediaType::JavaScript, &[]));
assert!(needs_compilation(false, MediaType::TypeScript, &[]));
assert!(needs_compilation(false, MediaType::JSX, &[]));
assert!(needs_compilation(false, MediaType::TSX, &[]));
assert!(needs_compilation(
false,
MediaType::JavaScript,
&[
&ModuleGraphFile {
specifier: "file:///some/file.ts".to_string(),
url: "file:///some/file.ts".to_string(),
redirect: None,
filename: "some/file.ts".to_string(),
imports: vec![],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
media_type: MediaType::TypeScript,
version_hash: "1".to_string(),
source_code: "function foo() {}".to_string(),
},
&ModuleGraphFile {
specifier: "file:///some/file1.js".to_string(),
url: "file:///some/file1.js".to_string(),
redirect: None,
filename: "some/file1.js".to_string(),
imports: vec![],
referenced_files: vec![],
lib_directives: vec![],
types_directives: vec![],
type_headers: vec![],
version_hash: "1".to_string(),
media_type: MediaType::JavaScript,
source_code: "function foo() {}".to_string(),
},
],
));
}