diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2020-10-13 13:35:35 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-13 13:35:35 +0200 |
commit | 0bd3cea0ff6d2d4840c0df2938b5ae5c5d7cc4bd (patch) | |
tree | fa9a635db4d34e0a30e13d37f40a0d7a4f92f44a /cli/program_state.rs | |
parent | 000ac5c40b71c38cc26a36e579fbb0936f0573d7 (diff) |
refactor(cli): rename GlobalState to ProgramState (#7914)
Diffstat (limited to 'cli/program_state.rs')
-rw-r--r-- | cli/program_state.rs | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/cli/program_state.rs b/cli/program_state.rs new file mode 100644 index 000000000..f81ed88bb --- /dev/null +++ b/cli/program_state.rs @@ -0,0 +1,538 @@ +// 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("--importmap") + } + 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, 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(), + }, + ], + )); +} |